Virtual Inheritance Designer Order

I am trying to better understand the concept of virtual inheritance and its dangers.

I read in another post ( Why is the default constructor called in virtual inheritance? ) That it (= virtual inheritance) changes the order in which the constructor is called ("grandma" is called first, whereas without virtual inheritance this is not so).

So, I tried the following to see that I have an idea (VS2013):

#define tracefunc printf(__FUNCTION__); printf("\r\n") struct A { A(){ tracefunc; } }; struct B1 : public A { B1(){ tracefunc; }; }; struct B2 : virtual public A { B2() { tracefunc; }; }; struct C1 : public B1 { C1() { tracefunc; }; }; struct C2 : virtual public B2 { C2() { tracefunc; }; }; int _tmain(int argc, _TCHAR* argv[]) { A* pa1 = new C1(); A* pa2 = new C2(); } 

Output:

 A::A B1::B1 C1::C1 A::A B2::B2 C2::C2 

This is not what I expected (I expected the order of the two classes to be different).

What am I missing? Can someone explain or direct me to a source that explains this better?

Thanks!

+7
c ++ inheritance virtual-inheritance
source share
3 answers

In your example, the expected result. Virtual inheritance comes into play when you have a class with multiple inheritance, the parent classes of which also inherit from the same class / type (ie, "diamond problem"). In your example, your classes can be configured to practically inherit (if necessary elsewhere in the code), but they are not necessarily โ€œactually inheritedโ€ based on your example, since none of the derived classes ( B1/B2/C1/C2 ) execute more than inherit directly from A

To expand, I modified your example to explain a little more:

 #include <cstdio> #define tracefunc printf(__FUNCTION__); printf("\r\n") struct A { A() { tracefunc; } virtual void write() { tracefunc; } virtual void read() { tracefunc; } }; struct B1 : public A { B1() { tracefunc; }; void read(){ tracefunc; } }; struct C1 : public A { C1() { tracefunc; }; void write(){ tracefunc; } }; struct B2 : virtual public A { B2() { tracefunc; }; void read(){ tracefunc; } }; struct C2 : virtual public A { C2() { tracefunc; }; void write(){ tracefunc; } }; // Z1 inherits from B1 and C1, both of which inherit from A; when a call is made to any // of the base function (ie A::read or A::write) from the derived class, the call is // ambiguous since B1 and C1 both have a 'copy' (ie vtable) for the A parent class. struct Z1 : public B1, public C1 { Z1() { tracefunc; } }; // Z2 inherits from B2 and C2, both of which inherit from A virtually; note that Z2 doesn't // need to inherit virtually from B2 or C2. Since B2 and C2 both virtual inherit from A, when // they are constructed, only 1 copy of the base A class is made and the vtable pointer info // is "shared" between the 2 base objects (B2 and C2), and the calls are no longer ambiguous struct Z2 : public B2, public C2 { Z2() { tracefunc; } }; int _tmain(int argc, _TCHAR* argv[]) { // gets 2 "copies" of the 'A' base since 'B1' and 'C1' don't virtually inherit from 'A' Z1 z1; // gets only 1 "copy" of 'A' base since 'B2' and 'C2' virtualy inherit from 'A' and thus "share" the vtable pointer to the 'A' base Z2 z2; z1.write(); // ambiguous call to write (which one is it .. B1::write() (since B1 inherits from A) or A::write() ?) z1.read(); // ambiguous call to read (which one is it .. C1::read() (since C1 inherits from A) or A::read() ?) z2.write(); // not ambiguous: z2.write() calls C2::write() since it "virtually mapped" to/from A::write() z2.read(); // not ambiguous: z2.read() calls B2::read() since it "virtually mapped" to/from A::read() return 0; } 

Although this may be "obvious" to us, the people we intend to do in the case of the variable z1 , since B1 does not have a write method, I would "expect" the compiler to choose the C1::write method, but because of how it works displaying the memory of objects, a problem arises, since the base copy of A in object C1 can have different information (pointers / links / descriptors) than the copy of base A in object B1 (since there are technically 2 copies of base A ); therefore calling B1::read() { this->write(); } B1::read() { this->write(); } may lead to unexpected behavior (although not undefined).

The virtual in the base class specifier makes it explicit that other classes that actually inherit from the same base type should receive only 1 copy of the base type.

Note that the above code should not compile with compiler errors, explaining the ambiguous calls for the z1 object. If you comment out the lines z1.write(); and z1.read(); , the conclusion (for me at least) is as follows:

 A::A B1::B1 A::A C1::C1 Z1::Z1 A::A B2::B2 C2::C2 Z2::Z2 C2::write B2::read 

Note the 2 calls to A ctor ( A::A ) before z1 is created, and Z2 has only 1 call to constructor A

I recommend reading the following on virtual inheritance , as it describes some other pitfalls in more detail to take note of (for example, the fact that actually inherited classes must use an initialization list to make calls to the ctor base class, or that you should avoid using casts like C when doing this type of inheritance).

This also explains a little more than what you originally hinted at ordering the constructor / destructor, or rather, how ordering is done using multiple virtual inheritances.

Hope this helps to sort things out a bit.

+2
source share

The output of the compiler is right. In fact, it is about the goal of virtual inheritance. Virtual inheritance aims to solve the Diamond problem in multiple inheritance. For example, B inherits from A, C inherits from A and D inherits from B, C. The diagram looks like this:

  A | | BC | | D 

So, D has two instances of A from B and C. If A has virtual functions, a problem arises.

For example:

 struct A { virtual void foo(){__builtin_printf("A");} virtual void bar(){} }; struct B : A { virtual void foo(){__builtin_printf("B");} }; struct C : A { virtual void bar(){} }; struct D : B, C { }; int main() { D d; d.foo(); // Error } 

If I use my xlC compiler to compile and run:

 xlC -+ aC 

The error message is as follows:

 aC:25:7: error: member 'foo' found in multiple base classes of different types d.foo(); // Error ^ aC:9:18: note: member found by ambiguous name lookup virtual void foo(){__builtin_printf("B");} ^ aC:3:18: note: member found by ambiguous name lookup virtual void foo(){__builtin_printf("A");} ^ 1 error generated. Error while processing aC 

The error message is very clear, the member 'foo' is found in several base classes of different types. If we add virtual inheritance, the problem will be solved. Since the rights to A are processed by D, there is one instance of A.

Back to your code, the inheritance diagram looks like this:

 AA | | B1 B2 | | C1 C2 

There is no problem with diamonds, this is only one inheritance. Thus, the construction order is also A-> B2-> C2, there is no difference in output.

+1
source share

You cannot see any difference in output, because the output will be the same in any of the following hiearchies of the class:

Hiearchy 1

 class A {}; class B2 : virtual public A {}; class C2 : virtual public B2 {}; 

Hiearchy 2

 class A {}; class B2 : public A {}; class C2 : virtual public B2 {}; 

Hiearchy 3

 class A {}; class B2 : virtual public A {}; class C2 : public B2 {}; 

Hiearchy 3

 class A {}; class B2 : public A {}; class C2 : public B2 {}; 

In all these cases, A::A() is executed first, then B2::B2() , and then C2::C2() .

The difference between the two is when it calls the A::A() call. Is it called from B2::B2() or C2::C2() ?

I am not 100% sure of the answer to Hiearchy 1 . I think B2::B2() should be called from C2::C2 , since B2 is the virtual base class of C A::A() must be called from B2:B2() , since A is the virtual base class of B2 . But I could be wrong in the exact order.

In Hierarchy 2 , A::A() will be called from B2::B2() . Since B2 is the base class of virtual C2 , B2::B2() is called from C2::C2() . Since A is a normal base class of B2 , A::A() is called from B2::B2() .

In Hierarchy 2 , A::A() will be called from C2::C2() . Since A is a virtual base class, A::A() is called from C2::C2() . B2::B2() is called after the completion of the call A::A() .

In Hierarchy 4 , A::A() will be called from B2::B2() . I think this case needs no explanation.

To clarify my doubts about Hiearchy 1 , I used the following program:

 #include <iostream> class A { public: A(char const *from) { std::cout << "Called from : " << from << std::endl; } }; class B2 : virtual public A { public: B2() : A("B2::B2()") {} }; class C2 : virtual public B2 { public: C2() : A("C2::C2()") {} }; int main() { C2 c; } 

I got the following output:

 Called from : C2::C2() 

This confirms that @TC is indicated in his comment, which is different from what I expected. A::A() is called from C2::C2 , not from B2::B2 .

0
source share

All Articles