I am quite interested about virtual inheritance
and this thing creates mystery for me. Let's consider an example with virtual inheritance
:
struct Base {
virtual void v() { std::cout << "v"; }
};
struct IntermediateDerivedFirst : virtual Base {
virtual void w() { std::cout << "w"; }
};
struct IntermediateDerivedSecond : virtual Base {
virtual void x() { std::cout << "x"; }
};
struct Derived : IntermediateDerivedFirst, IntermediateDerivedSecond {
virtual void y() { std::cout << "y"; }
};
Finally, Derived
should look like this:
--------
|[vtable]| -----> [ vbase offset 20 ]
|[vtable]|--- [ top offset 0 ]
|[vtable]|- | [ Derived typeinfo ]
-------- || [ IntermediateDerivedFirst::w() ]
|| [ Derived::y() ]
||
|----> [ vbase offset 12 ]
| [ top offset -8 ]
| [ Derived typeinfo ]
| [ IntermediateDerivedSecond::x()]
|
-----> [ vbase offset 0 ]
[ top offset -20 ]
[ Derived typeinfo ]
[ Base::v() ]
So, literally, virtual
inheritance moves vtable
for the most base class to the end and as we can see -- vtable
s for IntermediateDerivedFirst
, IntermediateDerivedSecond
do not contain address for Base
's v()
method. Okay, then, we can see that the class has few vtable
s.
Let's consider a code:
IntermediateDerivedFirst* fb = new Derived;
fb->v();
delete fb;
This invocation is still valid, however, vtable
for IntermediateDerivedFirst
has no info about v()
method and it seems it uses some magic here and it uses third vtable
pointer to invoke v()
.
So, how does the compiler choose needed vtable
pointer to get an address of function being invoked?
Bjarne Stroustroup wrote a detailed paper on solving the "diamond problem" in multiple inheritance using C++: Stroustrup, B Fall 1989, 'Multiple Inheritance for C++'. Computing Systems, Vol. 2 No. 4, p. 367-395
With the 'virtual Base' declared in both IntermediateDerived classes; you are guaranteed to get only a single instance of the common Base class.
From C++ Language / Classes / Derived classes
For each distinct base class that is specified virtual, the most derived object contains only one base class subobject of that type, even if the class appears many times in the inheritance hierarchy (as long as it is inherited virtual every time).
That is the compiler will provide one instance of each:
Both the IntermediateDerived_X classes will have a virtual pointer in their vtables that stores the offset to the Base class. When either IntermediateDerived_X class attempts to access Base::v(), it uses the virtual pointer in it's vtable to find the Base object. Along this same line; if Derived inherited 100 more IntermediateDerived_X classes with the same 'virtual Base,' there would still only ever be one Base instance. The call to Base::v() function uses the virtual pointer to access the instance of the Base class.
It is important to note that the Base object is only constructed once; when it is first initialized in the Derived class. Inherited classes will be constructed from Base to most derived. Conversely; the order of destruction will go from most derived to Base In the example instantiation; calling 'new Derived' will construct a single instance of each of the four classes is constructed.
Based on the address table above: IntermediateDerivedFirst has knowledge of 'vbase offset = 20', which is the location of the Base object vtable. IntermediateDerivedSecond could access the Base class in the same way (vbase offset = 12); and would result in the exact same functionality. There is not a magic third vtable, but simply a pointer to the vtable of Base shared between all derived classes.
From an implementation viewpoint; it is generally more memory savvy to add a single pointer to a Base class in all virtual classes than it would be to copy the virtual table to every derived class. Different compilers, optimizations, linkers etc. may implement or change the resulting vtables a little differently. But there will only be a single Base object pointed to by all derived classes using virtual inheritance.