The “Deadly Diamond of Death”
Consider a set of classes with the following inheritance hierarchy.
diamond.cpp
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
|
#include<iostream> using namespace std; class A { int value; public: A() { value = 5; cout << "A's constructor called" << endl; } void printValue(){ cout << value << endl; } }; class B: public A { public: B() { cout << "B's constructor called" << endl; } }; class C: public A { public: C() { cout << "C's constructor called" << endl; } }; class D: public B, public C { public: D() { cout << "D's constructor called" << endl; } }; int main() { D d; return 0; } |
Outputs:
|
A's constructor called B's constructor called A's constructor called C's constructor called D's constructor called |
Our class hierarchy ended up looking more like this:
This is because C++ by default uses replicated inheritance.
Let’s investigate further. Now add printValue() to D’s main:
|
int main() { D d; d.printValue(); return 0; } |
Output:
|
../diamond.cpp:47:4: error: non-static member 'printValue' found in multiple base-class subobjects of type 'A': class D -> class B -> class A class D -> class C -> class A d.printValue(); ^ ../diamond.cpp:19:7: note: member found by ambiguous name lookup void printValue(){ ^ 1 error generated. |
The same thing will happen if we try to add
to the main. We could try to tell the compiler which A we mean by saying something like:
|
A a = static_cast<B>(d); a.printValue(); |
which works, but doesn’t solve the problem that we have two different A’s.
Solution: use shared multiple inheritance – make B and C inherit from virtual A.
|
class B: virtual public A { ... } class C: virtual public A { ... } |
|
A's constructor called B's constructor called C's constructor called D's constructor called 5 |
The actual machinery behind all of this is quite complex, but you can read about it a bit here.