class A { public: void eat(){ cout<<"A";} };
class B: virtual public A { public: void eat(){ cout<<"B";} };
class C: virtual public A { public: void eat(){ cout<<"C";} };
class D: public B,C { public: void eat(){ cout<<"D";} };
int main(){
A *a = new D();
a->eat();
}
I understand the diamond problem, and above piece of code does not have that problem.
How exactly does virtual inheritance solve the problem?
What I understand:
When I say A *a = new D();
, the compiler wants to know if an object of type D
can be assigned to a pointer of type A
, but it has two paths that it can follow, but cannot decide by itself.
So, how does virtual inheritance resolve the issue (help compiler take the decision)?
This problem can solve by using Virtual keyword.
Example of Diamond Problem.
Instances of derived classes "contain" instances of base classes, so they look in memory like that:
Thus, without virtual inheritance, instance of class D would look like:
So, note two "copies" of A data. Virtual inheritance means that inside derived class there is a vtable pointer set at runtime that points to data of base class, so that instances of B, C and D classes look like:
Why another answer?
Well, many posts on SO and articles outside say, that diamond problem is solved by creating single instance of
A
instead of two (one for each parent ofD
), thus resolving ambiguity. However, this didn't give me comprehensive understanding of process, I ended up with even more questions likeB
andC
tries to create different instances ofA
e.g. calling parametrized constructor with different parameters (D::D(int x, int y): C(x), B(y) {}
)? Which instance ofA
will be chosen to become part ofD
?B
, but virtual one forC
? Is it enough for creating single instance ofA
inD
?Not being able to predict behavior without trying code samples means not understanding the concept. Below is what helped me to wrap head around virtual inheritance.
Double A
First, lets start with this code without virtual inheritance:
Lets go through output. Executing
B b(2);
createsA(2)
as expected, same forC c(3);
:D d(2, 3);
needs bothB
andC
, each of them creating its ownA
, so we have doubleA
ind
:That's the reason for
d.getX()
to cause compilation error as compiler can't choose whichA
instance it should call method for. Still it's possible to call methods directly for chosen parent class:Virtuality
Now lets add virtual inheritance. Using same code sample with the following changes:
Lets jump to creation of
d
:You can see,
A
is created with default constructor ignoring parameters passed from constructors ofB
andC
. As ambiguity is gone, all calls togetX()
return the same value:But what if we want to call parametrized constructor for
A
? It can be done by explicitly calling it from constructor ofD
:Normally, class can explicitly use constructors of direct parents only, but there is an exclusion for virtual inheritance case. Discovering this rule "clicked" for me and helped understanding virtual interfaces a lot:
Code
class B: virtual A
means, that any class inherited fromB
is now responsible for creatingA
by itself, sinceB
isn't going to do it automatically.With this statement in mind it's easy to answer all questions I had:
D
creation neitherB
norC
is responsible for parameters ofA
, it's totally up toD
only.C
will delegate creation ofA
toD
, butB
will create its own instance ofA
thus bringing diamond problem backYou want: (Achievable with virtual inheritance)
And not: (What happens without virtual inheritance)
Virtual inheritance means that there will be only 1 instance of the base
A
class not 2.Your type
D
would have 2 vtable pointers (you can see them in the first diagram), one forB
and one forC
who virtually inheritA
.D
's object size is increased because it stores 2 pointers now; however there is only oneA
now.So
B::A
andC::A
are the same and so there can be no ambiguous calls fromD
. If you don't use virtual inheritance you have the second diagram above. And any call to a member of A then becomes ambiguous and you need to specify which path you want to take.Wikipedia has another good rundown and example here
The correct code example is here. The diamond problem:
The solution :
The problem is not the path the compiler must follow. The problem is the endpoint of that path: the result of the cast. When it comes to type conversions, the path does not matter, only the final result does.
If you use ordinary inheritance, each path has it's own distinctive endpoint, meaning that the result of the cast is ambiguous, which is the problem.
If you use virtual inheritance, you get a diamond-shaped hierarchy: both paths leads to the same endpoint. In this case the problem of choosing the path no longer exists (or, more precisely, no longer matters), because both paths lead to the same result. The result is no longer ambiguous - that is what matters. The exact path doesn't.