c++inheritancevtablememory-layoutvptr

Can derived classes have more than one pointer to a virtual table?


I am watching the BackToBasics talk: Virtual Dispatch and Its Alternatives from CppCon2019. The presenter says and the slide shows (assuming I haven't misunderstood) that a derived class inherits a vtable pointer from the base class and additionally has its own vptr.

Of course, technically this isn't mandated by the standard but I am getting myself a bit confused and my experiments with sizeof() also seem to imply there should only need to be one pointer. Please can someone clarify if there are any situations where multiple vptrs are needed?

Thanks

P.S. Just to be clear, in this context we are considering the more common public inheritance and not virtual or multiple inheritance (the presenter explicitly mentions this in an earlier part of the talk).


Solution

  • The a vtable contains the address of each virtual function for the class at a known offset.

    [Remark: In practice unlike a regular class, vtables have members at negative offset, much like a pointer in a the middle of a array. That is just a convention that doesn't change implementation freedom much. Anyway the only issue is that the placement of an information in a vtable is legislated by a convention (the ABI) and compilers by following the same one produce compatible code for polymorphic classes.]

    What happens when you have additional functions in a derived class? (not just the functions "inherited" from the base class)

    Once you accept the idea that a pointer to a structure both points to the whole object and to its first member, you have the idea that a pointer to derived class points to a base class that is appropriately located at offset zero. So you can have the exact same pointer value, as represented as a void*, that can be used alternatively for a derived object or a base under this convention for single inheritance.

    Now you can apply that to any data structure and even to a vtable which is really not a table (array of elements of the same type, or values that can be interpreted in the same way) but a record (of objects of unrelated type or meaning); you can see that a vtable for such derived class can just be derived from the vtable of its unique base in the exact same way.

    (Note that if you compile C++ to C, you might run into type aliasing rules when you do such things. Of course assembly has no such problem, nor naively compiled "high level assembler" C.)

    So for single inheritance the base is integrated and optimized into the derived class:

    Note that placing the base at offset zero allows you to place vtable base at zero offset, which in turn allows you to use the same vptr but does not imply it; conversely sharing the vptr with a base implies that the base vtable is at offset zero (vtable layout = meta class level) so the base must be at offset zero (data members layout = class level).

    And multiple inheritance is actually single inheritance plus, as one class is always treated as privileged: it is placed at offset zero so the pointers are the same, so the vtable can be placed at offset zero (because the pointers are the same); others bases, not so.

    As we see, all but one of the inherited polymorphic classes are placed at a non zero offset in multiple inheritance. Each one carries an additional "inherited" vptr in the derived class; that (hidden) pointer member must be correctly filled by any derived constructor.

    These additional vptr are for base classes that occur at non zero offset, so a pointer to an inherited base must be adjusted (add a positive constant to convert to base pointer, remove it to convert back). That a compiler needs to produce code to perform an implicit conversion is a trivial remark (converting an integer to a floating point type is a much more involved task); but here the conversion of this is between a function call on a given base type and landing in the function that is an overrider in a base or derived class: the difference is that adjustment depends on function overriding which is only known for a class (an instance of a meta type). So the vptr needs to point to distinct vtable information: one that knows how to deal with these base to derived pointer conversions.

    As instances of the "meta type", vtables have all the information to do all pointers adjustment automatically. (These depend on the specific class types involved, and on no other factor.)

    So at implementation level, the two types of inheritances are:

    This is for the basic stuff. Virtual inheritance is a lot more subtle at the implementation level, and even the concept of primary isn't so clear, as virtual bases can be "primary" of a derived class only in some more derived classes!