First: without virtual functions, the likelihood that vptr
generally in classes. The 8 bytes you see are an artifact about how virtual inheritance is implemented.
Often, you can use the same vptr
for multiple classes in a hierarchy. For this, it is necessary that their offset to the final class must be the same, and for the list of records vtable to the base class, to be the initial sequence, the list of records vtable to the derived class.
Both conditions are satisfied in almost all implementations for single inheritance. No matter how deep the legacy is, there will usually be only one vptr
shared by all classes.
In the case of multiple inheritance, there will always be at least one class for which these requirements are not met, since two base classes cannot have a common starting address, and if they do not have exactly the same virtual functions, only one vtable could be the initial the sequence is different.
Virtual inheritance adds another quirk, since the position of the virtual base relative to the class inherited from it will vary depending on the rest of the hierarchy. Most of the implementations that I have seen use a separate pointer for this, although this information should also be found in table v.
If we take your hierarchy by adding virtual functions so that we have some with vptr
, we notice that B
and D
can still share vtable
, but separate A
and C
required for both A
and C
This means that if your classes had virtual functions, you will need at least three vptr
. (From this I conclude that your implementation uses separate pointers to a virtual base. When using B
and D
the same pointer and C
with its own pointer. And, of course, A
does not have a virtual base and does not need a pointer to itself.)
If you are trying to accurately analyze what is happening, I would suggest adding a new virtual function in each class and adding a pointer-size integral type that you start with a different known value for each class. (Use constructors to set the value.) Then create an instance of the class, take its address, then output the address for each base class. And then drop the class: known fixed values will help you determine where the different elements are. Sort of:
struct VB { int vb; VB() : vb( 1 ) {} virtual ~VB() {} virtual void fvb() {} }; struct Left : virtual VB { int left; Left() : left( 2 ) {} virtual ~Left() {} virtual void fvb() {} virtual void fleft() {} }; struct Right : virtual VB { int right; Right() : right( 3 ) {} virtual ~Right() {} virtual void fvb() {} virtual void fright() {} }; struct Derived : Left, Right { int derived; Derived() : derived( 5 ) {} virtual ~Derived() {} virtual void fvb() {} virtual void fleft() {} virtual void fright() {} virtual void fderived() {} };
You might want to add Derived2
, which comes from Derived
and see what happens with relative addresses between, for example, Left
and VB
depending on whether the object is of type Derived
or Derived2
.