Multiple and virtual inheritance - dreaded diamond

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
#include <iostream>

class Base
{
  protected:
    int data;
  public:
    Base () {}
    Base (int d) { data = d; }
    virtual ~Base () {}
    int getData () const { return data; }
};

class Der1 : public virtual Base //virtual inheritance
{
  public:
    Der1 () :Base() {}
    Der1 (int d) :Base(d) {}
    virtual ~Der1 () {}
};

class Der2 : public virtual Base
{
  public:
    Der2 () :Base() {}
    Der2 (int d) :Base(d) {}
    virtual ~Der2 () {}
};

class Joined : public Der1, public Der2 //multiple inheritance
{
  public:
    Joined () :Der1(), Der2() {}
    Joined (int d) :Der1(d), Der2(d) {}
    virtual ~Joined () {}
};

int main ()
{
  Base* b = new Joined(4);
  std::cout << b->getData() << std::endl;
  delete b;
}


I have a question - why does this output 0 instead of 4 like it should? If I add a proper Base constructor to the initializer lists in the Joined class, it outputs 4 as expected, but if you get too deep, you end up messing things up. That is, suppose I had the same thing as above, but twice, where two new classes virtually inherited Joined, making Joined the new base class. Then I would have another class that inherited the two new classes. The constructors that would be needed in the initializer lists would be Base, Joined, NewDer1 and NewDer2, right? To me, that seems like multiple inheritance isn't really worth it.

I realize that it shouldn't be necessary to get this deep into it, but when I think about how Java is designed, I really must wonder if it can be done without requiring Base, Joined, NewJoined, NewNewJoined, NewNewNewDer1, NewNewNewDer2 when you get to the end of a fourth diamond (NewNewNewJoined).

Thanks! Note - this is purely a learning experience for me, so feel free to flame away. ^_^
See this thread: http://www.cplusplus.com/forum/general/1414/

The basic issue is that virtual inheritance means Der1 and Der2 share the same instance of Base when they are part of Joined. C++ says that Der1 and Der2 do not initialise Base because that would mean one instance of Base having its constructor run twice. Thus Joined must initialise Base itself.

You need:
 
Joined (int d) : Der1(d), Der2(d), Base(d) {}


PS. Thinking about, it although...
1
2
3
4
Joined (int d) : Der1(d), Der2(d), Base(d) {}
Joined (int d) : Der2(d), Base(d), Dir1(d) {}
Joined (int d) : Base(d), Der1(d), Dir2(d) {}
// and so on 

... are all equivalent, its good practise to write...
 
Joined (int d) : Base(d), Der1(d), Dir2(d) {}
Last edited on
Ah, thanks for that explanation, DrDogg. It helped a lot.

What I don't understand is if I have the Joined class and I create two classes that virtually inherit Joined. Then I create a class that inherits both of those classes. It is essentially the first diamond attached to this new diamond, where Joined is in the middle joining the two diamonds.

It seems like I need to use this for the last class definition to make things work as expected:
1
2
3
4
5
6
7
class Final : public JoinedDer1, public JoinedDer2 //the same idea as with Der1 and Der2
{
  public:
    Final () :Base(), Joined(), JoinedDer1(), JoinedDer2() {}
    Final (int d) :Base(d), Joined(d), JoinedDer1(d), JoinedDer2(d) {}
    ~Final () {}
};

Why do I still need a Base constructor if the Joined constructor creates an instance of Base for me? Is this due to the fact that Joined is virtually inherited, and doesn't do what the code says to do? I think this rules out multiple inheritance when it comes to writing libraries if it is done this way...

Thanks again!

P.S. This is what I am talking about when I am referring to a diamond attached to another diamond (read from left to right):
1
2
3
4
5
     Der1        JoinedDer1
    /    \      /          \
Base      Joined            Final
    \    /      \          /
     Der2        JoinedDer2

Der1, Der2, JoinedDer1, JoinedDer2 all virtually inherit their parent.
Last edited on
Try out initializing the base during the construction of Joined, meaning call the base's constructor in Joined using the parameter supplied from the leaf, Final.

Try it out, and post the findings.

Good day :)
> Try out initializing the base during the construction of Joined,
> meaning call the base's constructor in Joined using the parameter
> supplied from the leaf, Final.

I'm not quite sure what you meant, but I found that even though I'm using the following code, the constructor for Final(int d) still uses the Base constructor Base():
1
2
3
4
5
6
7
Joined () :Base(), Der1(), Der2() {}
Joined (int d) :Base(d), Der1(d), Der2(d) {}

//skipping to the Final class

Final () :Joined(), JoinedDer1(), JoinedDer2() {}
Final (int d) :Joined(d), JoinedDer1(d), JoinedDer2(d) {}


When I use Joined(d), it uses Joined(int d) for the Joined constructor, but for some reason the Base constructor Base() is called rather than Base(int d). Is this a problem with my compiler or my thinking?

I added some lines like
std::cerr << "Base()" << std::endl;
and
std::cerr << "Base(int d)" << std::endl;

where appropriate, along with similar ones for the destructor of each class. Here is the updated main() function that I am using:
1
2
3
4
5
6
int main (void)
{
  Base* b = new Final(4);
  std::cout << b->getData() << std::endl;
  delete b;
}


This is the output I get with the constructors and destructors outputting information:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Base()
  Der1(int d)
    Der2(int d)
      Joined(int d)
        JoinedDer1(int d)
          JoinedDer2(int d)
            Final(int d)
0
            ~Final()
          ~JoinedDer2()
        ~JoinedDer1()
      ~Joined()
    ~Der2()
  ~Der1()
~Base()

Everything is being destroyed in order, so I must be missing something.

Edit: It is almost as if 'int data;' isn't cascading down. Is there a "trick" to get that to work or something?
Last edited on
@rpgfan3233
>>Try out initializing the base during the construction of Joined, meaning call the base's constructor in Joined using the parameter supplied from the leaf, Final.

Sorry for my hasty typing. What I meant was that initializing base during the construction of itself and Joined, meaning call the base’s initialization method that uses parameters supplied from the leaf, Final.

In fact, it is NOT recommend initializing the virtual base class constructor from the derived. Though the compiler may not complain, it is actually a design issue. Please read below I explain for DrDogg also.


@DrDogg
>>C++ says that Der1 and Der2 do not initialise Base because that would mean one instance of Base having its constructor run twice. Thus Joined must initialise Base itself

Your view/note conflicts with what actually C++ does. Please check it again.

In general, when a class is multiply and virtually inherited, C++ compiler considers only one copy of the parent used for inheritance thus leading to call the parent's constructor only once. Hence it initializes itself by calling its default constructor and ignoring your initialization call for the base class.

It is actually a design issue. If this is not taken into consideration, the designer/developer would have made a terrible mistake and would see wrong results.

Take an example, what is the guarantee that same value of a parameter is supplied to the virtual parent/base class from two different derived classes.

For example, run the code the see the output yourself. It will not print any output based on your supplied parameters.

With and without default constructors for each.


#include <iostream>
using namespace std;

class Base
{
public:
Base() { cout << "Base()" << endl; a = 0; }
Base(int c) { cout << "Base(int)" << endl; a = c; }

void print_a( ) { cout << "a: " << a << endl; }
~Base() { cout << "~Base" << endl; }
private:
int a;
};


class Derived1 : public virtual Base
{
public:
Derived1() { cout << "Derived1()" << endl; a1 = 0; }
//any guarantee that this c+10 goes to one copy of virtual base?
//Derived1(int c) : Base(c+10) { cout << "Derived1(int)" << endl; a1 = c; }
Derived1(int c) : Base(c) { cout << "Derived1(int)" << endl; a1 = c; }

void print_a1( ) { cout << "a1: " << a1 << endl; }
~Derived1() { cout << "~Derived1" << endl; }
private:
int a1;
};


class Derived2 : public virtual Base
{
public:
Derived2() { cout << "Derived2()" << endl; a2= 0; }
//different from other child to the same virtual parent, which one to take?
//Derived2(int c) : Base(c+20) { cout << "Derived2(int)" << endl; a2= c; }
Derived2(int c) : Base(c) { cout << "Derived2(int)" << endl; a2= c; }
void print_a2( ) { cout << "a2: " << a2 << endl; }
~Derived2() { cout << "~Derived2" << endl; }

private:
int a2;
};


class Final : public Derived1, public Derived2
{
public:
//Final() { cout << "Final()" << endl; x= 0; }
Final(int c) { cout << "Final(int)" << endl; x= c; }
void print_x( ) { cout << "x: " << x << endl; }
~Final() { cout << "~Final" << endl; }

private:
int x;
};


int main()
{
Final f4(10);
f4.print_a();
f4.print_a1();
f4.print_a2();
f4.print_x();
}



To overcome the initialization for base class issue, I would suggest writing a protected method in base class and call it from derived as a regular function call to its parent/base class.

Try it out. Good luck :)
Please run the code and check the output how the initialization works to justify my point.

Same code, again: (indented)



#include <iostream>
using namespace std;

class Base
{
public:
Base() { cout << "Base()" << endl; a = 0; }
Base(int c) { cout << "Base(int)" << endl; a = c; }

void print_a( ) { cout << "a: " << a << endl; }
~Base() { cout << "~Base" << endl; }
private:
int a;
};


class Derived1 : public virtual Base
{
public:
Derived1() { cout << "Derived1()" << endl; a1 = 0; }
//any guarantee that this c+10 goes to one copy of virtual base?
//Derived1(int c) : Base(c+10) { cout << "Derived1(int)" << endl; a1 = c; }
Derived1(int c) : Base(c) { cout << "Derived1(int)" << endl; a1 = c; }

void print_a1( ) { cout << "a1: " << a1 << endl; }
~Derived1() { cout << "~Derived1" << endl; }
private:
int a1;
};


class Derived2 : public virtual Base
{
public:
Derived2() { cout << "Derived2()" << endl; a2= 0; }
//different from other child to the same virtual parent, which one to take?
//Derived2(int c) : Base(c+20) { cout << "Derived2(int)" << endl; a2= c; }
Derived2(int c) : Base(c) { cout << "Derived2(int)" << endl; a2= c; }
void print_a2( ) { cout << "a2: " << a2 << endl; }
~Derived2() { cout << "~Derived2" << endl; }

private:
int a2;
};


class Final : public Derived1, public Derived2
{
public:
//Final() { cout << "Final()" << endl; x= 0; }
Final(int c) { cout << "Final(int)" << endl; x= c; }
void print_x( ) { cout << "x: " << x << endl; }
~Final() { cout << "~Final" << endl; }

private:
int x;
};


int main()
{
Final f4(10);
f4.print_a();
f4.print_a1();
f4.print_a2();
f4.print_x();
}
Sorry, there is a typo.
>>Final(int c) { cout << "Final(int)" << endl; x= c; }


should have been:
Final(int c) : Derived1(c), Derived(2) { cout << "Final(int)" << endl; x= c; }


for our discussion sake.

Passing the parameters to the Derived ones would pass up to Base :)



What a heck, again a typo :(

Final(int c) : Derived1(c), Derived2(c) { cout << "Final(int)" << endl; x= c; }


I found /a/ solution, but I'm not fond of it. I could write 'data = d;' in the Joined (int d) constructor, but that is only a temporary solution. If I needed to inherit from that and created another diamond (Final), then it would work. But if I inherited the same way from Final, it would stop working unless I did the same thing for Final. This violates the concept of hiding the implementation from the programmer. After all, if I design something like that, I don't want someone to wonder why their diamond(s), which inherits my own diamond(s), doesn't work because of this issue.

Is this a problem with the way C++ is designed, a problem with the diamond structure or a combination of both problems, or is it still just me not thinking enough? Thanks for the help so far, everybody. I'm not sure how far we're getting, but I'm definitely learning! ^_^
Check this one and run it.. As I suggested before:

#include <iostream>
using namespace std;

class Base
{
public:
Base() { cout << "Base()" << endl; a = 0; }
Base(int c) { cout << "Base(int)" << endl; a = c; }
void print_a( ) { cout << "a: " << a << endl; }
~Base() { cout << "~Base" << endl; }
private:
int a;
protected: // called only from itself and derived
void initVars(int x = 0) { cout << "initVars(int=0) - x: " << x << endl; a = x; }
};


class Derived1 : public virtual Base
{
public:
Derived1() { cout << "Derived1()" << endl; a1 = 0; }
Derived1(int c) { cout << "Derived1(int)" << endl; a1 = c; }
void print_a1( ) { cout << "a1: " << a1 << endl; }
~Derived1() { cout << "~Derived1" << endl; }
private:
int a1;
};


class Derived2 : public virtual Base
{
public:
Derived2() { cout << "Derived2()" << endl; a2= 0; }
Derived2(int c) { cout << "Derived2(int)" << endl; a2= c; }
void print_a2( ) { cout << "a2: " << a2 << endl; }
~Derived2() { cout << "~Derived2" << endl; }

private:
int a2;
};


class Final : public Derived1, public Derived2
{
public:
//call the initVars() for required initialization for this object hierarchy from here
Final() { initVars(); cout << "Final()" << endl; x= 0; } //default is always recommended :)
Final(int c) : Derived1(c), Derived2(c) { initVars(c); cout << "Final(int)" << endl; x= c; }
void print_x( ) { cout << "x: " << x << endl; }
~Final() { cout << "~Final" << endl; }

private:
int x;
};


int main()
{
Final f4(10);
f4.print_a();
f4.print_a1();
f4.print_a2();
f4.print_x();
}


The output:

Base()
Derived1(int)
Derived2(int)
initVars(int=0) - x: 10
Final(int)
a: 10
a1: 10
a2: 10
x: 10
~Final
~Derived2
~Derived1
~Base


As you notice it in the above output the initVars() is called only once from the leaf, ie, Final but initialized the parent’s data member.
If we call the same from Derived1(int) and Derived2(int) constructors, then it would be twice causing a overwrite on the base class data. This is very hazardous and memory-leak prone when particularly a pointer initialization is involved.

As only one copy of the virtual base class is created for multiple derived, and the base class’s default constructor is called to avoid conflicts, the above work-around would work to do the same initialization job.
Keep the initVars() kind of initialization method as protected, so it would be accessible to all its children through the last leaf in the hierarchy;

However the designer/developer should keep in mind that ONLY ONE copy of virtual base and only DEFAULT CONSTRCTOR is called during the object construction and design/develop the code accordingly with extra paid attention.

Good luck :)

@satm2008:
Can you please start using the "Insert Code" button?
Ooops, sorry, overlooked :)
I found the flaw(s) in my thinking by reading http://www.ddj.com/cpp/184402074?pgno=1

> In B and C, we know the default constructors will not invoke the
> A constructor, but we have to supply it anyway. This is pretty
> quirky. In fact, the use of virtual base classes and their need to
> be initialized by the most derived class can come as a shock to
> someone who thinks they are doing straightforward single
> inheritance.

Because Base is virtually inherited, its constructors are not called in the Der1 and Der2 classes, despite the fact that the constructors are there in the initializer list. As a result, the most derived class must call the constructors. There isn't a need for an initVars() function if this rule is applied. As long as the proper constructor is called, it will work just fine. In other words, Joined would call the Base constructor, Final would call the Base constructor, and subsequent classes would need to call the Base constructor.

Virtual inheritance seems powerful, but rather useless if this is the way it behaves. I can understand why it happens, but I wish the compiler would do what I tell it to do, not what it is supposed to do to avoid conflicts. :P Wishful thinking, huh?

I guess one good rule to remember is to avoid the diamond pattern when designing libraries.
Yes, thats right.
I suggested work around of InitVars() for calling base class initialization when it is not initialized by its constructor due to its virtuality.

Though I am still not so comfortable with the initVars() idea, it is just a work-around. We would have to be causious in the sense that the base class (values) could be altered by another initVars() call from another derived at same level.

It is in fact a design issue, as I mentioned earlier. It depends on what variables need to be initialized and how it is handled out of the constructor class.

Good luck :)






Topic archived. No new replies allowed.