What is happening behind the scene.
The simple explanation is that, because inheritance from Base
is virtual in both Der1
and Der2
, there is a single instance of the object in the most derived object Join
. At compile time, and assuming (which is the common case) virtual tables as dispatch mechanism, when compiling Der1::foo
it will redirect the call to bar()
through the vtable.
Now the question is how the compiler generates vtables for each of the objects, the vtable for Base
will contain two null pointers, the vtable for Der1
will contain Der1::foo
and a null pointer and the vtable for Der2
will contain a null pointer and Der2::bar
[*]
Now, because of virtual inheritance in the previous level, when the compiler processes Join
it will create a single Base
object, and thus a single vtable for the Base
subojbect of Join
. It effectively merges the vtables of Der1
and Der2
and produces a vtable that contains pointers to Der1::foo
and Der2::bar
.
So the code in Der1::foo
will dispatch through Join
‘s vtable to the final overrider, which in this case is in a different branch of the virtual inheritance hierarchy.
If you add a Der3
class, and that class defines either of the virtual functions, the compiler will not be able to cleanly merge the three vtables and will complain, with some error relating to the ambiguity of the multiply defined method (none of the overriders can be considered to be the final overrider). If you add the same method to Join
, then the ambiguity will no longer be a problem, as the final overrider will be the member function defined in Join
, so the compiler is able to generate the virtual table.
[*] Most compilers will not write null pointers here, but rather a pointer to a generic function that will print an error message and terminate
the application, allowing for better diagnostics than a plain segmentation fault.