Casting subclasses via an interface

Oct 7, 2019 at 8:56pm
I wrote some code in c# that I am porting to c++. I have a base class which contains the default implementation of a method (BaseClass::getTagName()), and some of the subclasses define their own overload of this method and some don't.

There is an array of several subclasses of this base class, and I want to be able to call the getTagName() method on each of them.

In c# I just have to create an interface with the toDer() function and then cast all the subclasses to that interface, and it will call the appropriate function, either the overload if it's present, or the default implementation in the base class.

How is this done in c++?

This is what it feels like it should look like


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
#include <bits/stdc++.h>

using namespace std;

class IBaseClass
{
public:
    virtual string getTagName() = 0;
};

class BaseClass : public IBaseClass
{
public:
    string getTagName();

protected:
    string tagName;
};

string BaseClass::getTagName()
{
    return this->tagName;
}

class SubclassA : public BaseClass, public IBaseClass
{
public:
    SubclassA();
};

class SubclassB : public BaseClass, public IBaseClass
{
public:
    SubclassB();
    string getTagName();
};

SubclassA::SubclassA()
{
    this->tagName = string("Subclass A");
}

SubclassB::SubclassB()
{
    this->tagName = string("Subclass B");
}

string SubclassB::getTagName()
{
    return this->tagName;
}



There are problems with this, and I am getting warnings:

1
2
direct base ‘IBaseClass’ inaccessible in ‘SubclassA’ due to ambiguity
direct base ‘IBaseClass’ inaccessible in ‘SubclassB’ due to ambiguity


I feel there is something basic that I am missing here, and if anyone can point me to the right topic in the docs, or explain it out, I would appreciate it.
Oct 7, 2019 at 9:22pm
You're already inheriting the interface class from inheriting BaseClass, so it's redundant (and ambiguous) to also inherit the interface again directly.

PS: Consider using the override specifier when overriding base-class member functions.
https://en.cppreference.com/w/cpp/language/override
This will ensure that when you declare a function, it is actually overriding an existing base-class function. (If you specify override for a function that doesn't exist to be overridden, it will correctly be a compiler error)

______________________________

Because C# doesn't have multiple inheritance, it avoids this ambiguity, and allows the redundant inheritance declarations. But of course the use of "virtual" and "override" are still needed to actually have correct polymorphism!

(I actually dislike that C# doesn't have a nice, opt-in way of specifying that an interface is being implemented, like C++'s "override".)
Last edited on Oct 7, 2019 at 10:11pm
Oct 8, 2019 at 4:30pm
Thanks for your response :)

Part of the question I'm wondering about is that I want to have a vector containing SubclassA, SubclassB...SubclassN objects. I can't cast it to a vector<BaseClass> because the overridden method getTagName won't be called and it will call the default implementation in BaseClass. So I don't think I want a virtual method since I want a default implementation.

In the above example I would have tried to put the subclasses in a vector<IBaseClass>. I didn't think there would be ambiguity because IBaseClass is virtual and therefore cannot have an implementation.

So the real question is this:

How can I have a collection of the subclasses and be able to call the getTagName in the subclass if its implemented there, or go up the inheritance chain to the base class otherwise?
Oct 8, 2019 at 6:10pm
Yep, dealing with dynamic collections of objects is when polymorphism is the most useful.

In C#, you can think of everything as being a pointer, but in C++, it isn't.
Polymorphism must be triggered through a reference or pointer to a polymorphic object.
If you have a vector<BaseClass>, and you try to copy a DerivedClass into it, object slicing will occur, and all the subclass information will be lost.

And because you can't have vectors of references, the only option left is to have a vector of pointers.

Here is a small example. Note that I use smart pointers here because they take care of the memory management for you. You don't actually need to use dynamic storage of the objects if you allocate everything beforehand. Actually, I'll show two examples. One with smart pointers, one without.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// Example program
#include <iostream>
#include <vector>
#include <memory>

class BaseClass {
  public: 
    virtual void printName() const=0;
    
    virtual ~BaseClass() { }
};

class SubClassA : public BaseClass {
    
    virtual void printName() const override
    {
        std::cout << "SubClassA's getName() called\n";   
    }
};

class SubClassB : public BaseClass {
    
    virtual void printName() const override
    {
        std::cout << "SubClassB's getName() called\n";   
    }
};

int main()
{
    using std::vector;
    
    std::cout << "\n(1)\n";
    //
    // using smart pointers:
    //
    {
        vector<std::unique_ptr<BaseClass>> vec;
        vec.push_back(std::make_unique<SubClassA>());
        vec.push_back(std::make_unique<SubClassB>());
        vec.push_back(std::make_unique<SubClassB>());
        vec.push_back(std::make_unique<SubClassA>());
        
        for (const auto& element : vec)
        {
            element->printName();   
        }

        // (clean-up happens automatically)
    }
    
    std::cout << "\n(2)\n";
    
    //
    // without using smart pointers, or dynamic allocation of individual objects:
    //
    {       
        SubClassA a_1;
        SubClassB b_1;
        SubClassA a_2;
        SubClassB b_2;
        
        vector<BaseClass*> vec = { &a_1, &b_1, &a_2, &b_2 };
        
        for (const BaseClass* element : vec)
        {
            element->printName();   
        }

        // (clean-up happens automtically; objects weren't dynamically allocated)
    } 
    
    std::cout << "\n(3)\n";
    
    //
    // using raw new/delete (NOT RECOMMENDED):
    //
    {       
        BaseClass* a_1 = new SubClassA();
        BaseClass* b_1 = new SubClassB();
        BaseClass* a_2 = new SubClassA();
        BaseClass* b_2 = new SubClassB();
        
        vector<BaseClass*> vec = { a_1, b_1, a_2, b_2 };
        
        for (const BaseClass* element : vec)
        {
            element->printName();   
        }
        
        delete a_1;
        delete b_1;
        delete a_2;
        delete b_2;
    } 
}


Edit: Added virtual base-class destructor.. you need those too.
Last edited on Oct 8, 2019 at 6:21pm
Oct 8, 2019 at 6:23pm
So I don't think I want a virtual method since I want a default implementation.

If not polymorphism... What else? I’m not that sure I understand your question.

At first sight, I’d say:
- create an ABC Base class;
- create a BaseVariant class which inherits Base and add getTagName();
- inherits the classes you want to have got getTagName() from BaseVariant and the others from Base;
- you can anyway override methods, even if not virtual (but if they are not virtual you should not mess with polymorphism!) - unless you state them final, of course.

Here a (pointless) example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <iostream>
#include <string>
#include <vector>


class BaseClass {
public:
    virtual std::string getTagName() const = 0;
    virtual ~BaseClass() = default;

protected:
    std::string tag_name { "BaseClass" };
};


class BaseClassVariant {
public:
    virtual std::string getTagName() const;

protected:
    std::string tag_name { "BaseClass" };
};


std::string BaseClassVariant::getTagName() const
{
    return tag_name;
}


class SubclassA : public BaseClassVariant {
public:
    SubclassA();
};


SubclassA::SubclassA()
{
    tag_name = "Subclass A";
}


class SubclassB : public BaseClass {
public:
    SubclassB();
    std::string getTagName() const;
};

SubclassB::SubclassB()
{
    tag_name = "Subclass B";
}


std::string SubclassB::getTagName() const
{
    return std::string("Believe or not: ") + tag_name;
}


int main()
{
    // Polymorphism?
    std::vector<BaseClass *> v; // DON'T! Use smart pointers instead

    // Pretty horrible:
    v.push_back( reinterpret_cast<BaseClass *>(new SubclassA) );
    v.push_back( reinterpret_cast<BaseClass *>(new SubclassB) );
    std::cout << "v.at(0)->getTagName(): " << v.at(0)->getTagName()
              << "\nv.at(1)->getTagName(): " << v.at(1)->getTagName()
              << '\n';
}


However, if I may venture a personal opinion: snippet of codes that don’t compile make you only waste your time.
The compiler is your best friend: do try to make your example compilable and get compiler advice. IMHO.
Oct 8, 2019 at 6:30pm
Even though you wrote "pretty horrible", I don't understand what you're trying to show. It's undefined behavior (even if it might work).

I think the issue is oldnoob was slicing the subclass objects by not using a vector of pointers.
Last edited on Oct 8, 2019 at 6:49pm
Oct 8, 2019 at 6:50pm
Ganado wrote:
It's undefined behavior

Is it?

I was just trying to stuck to the basic idea (raw pointers to represent polymorphism). Your example with smart pointers is far better, of course.

I just focused on the question «How to have a default implementation of a method for some, but not all the derived classes?». Maybe I misunderstood the request?
The lines in main() are there just to get some output.

- - -
Hijacking again another user’s thread :-) (sorry for that!) may I ask you why that’s undefined behaviour (obviously I don’t take care of memory allocation/deallocation - that’s a simplification)?
Many thanks.
Oct 8, 2019 at 7:01pm
Good question, my gut feeling was that is definitely a form of "type punning" and undefined behavior (though GCC might still be OK with it), but I don't have a source handy on me. Yeah sorry as well, hope we aren't confusing oldnoob more :) But to repeat myself (I edited my previous post a bit late): I think the issue is oldnoob was slicing the subclass objects by not using a vector of pointers.
Last edited on Oct 8, 2019 at 7:02pm
Oct 8, 2019 at 7:02pm
Oh, it should have been dynamic_cast, shouldn’t it? Very sorry for that, I mixed the names.
Oct 8, 2019 at 7:16pm
And dynamic_casting to an ABC fails, so, yes, totally junk code. My humblest apologies!

Ganado wrote:
slicing the subclass objects by not using a vector of pointers.

Just to be clear, when I upload my post, the website wasn’t showing me yours, even if I had prevously refreshed the page. I was just trying to add my opinion, not to contradict yours.

I think you are right, even if object slicing refers more to class properties than methods.

Oct 8, 2019 at 8:14pm
No worries, I didn't interpret anything said as being contradicting.
The v-table is not copied when a subclass object is copied into a base class object.
Last edited on Oct 8, 2019 at 8:16pm
Topic archived. No new replies allowed.