c++vtablevirtual-inheritancememory-layoutvptr

Virtual inheritance and empty vtable in base class


There is this code:

#include <iostream>

class Base
{
   int x;
};

class Derived : virtual public Base
{
   int y;
};

int main()
{
    std::cout << sizeof(Derived) << std::endl; // prints 12
    return 0;   
}

I have read that when some class is virtually inherited then there is created empty vtable for class Derived, so memory layout is as follows:

Derived::ptr to empty vtable
Derived::y
Base::x

and it is 12 bytes. The question is - what is purpose of this empty vtable if there are not any virtual methods and how is it used?


Solution

  • Derived needs some way to know where the Base subobject is. With virtual inheritance, the relative location of the base class is not fixed with respect to the location of the derived class: it may be located anywhere in the full object.

    Consider a more typical example involving diamond inheritance.

    struct A
    {
        int a;
    };
    
    struct B1 : virtual A
    {
        int b1;
    };
    
    struct B2 : virtual A
    {
        int b2;
    };
    
    struct C : B1, B2
    {
        int c;
    };
    

    Here, both B1 and B2 derive virtually from A, so in C, there is exactly one A subobject. Both B1 and B2 need to know how to find that A subobject (so that they can access the a member variable, or other members of A if we were to define them).

    This is what the vtable is used for in this case: both B1 and B2 will have a vtable that contains the offset of the A subobject.


    To demonstrate what a compiler might do to implement the above diamond inheritance example, consider the following class layouts and virtual tables, generated by the Visual C++ 11 Developer Preview.

    class A size(4):
            +---
     0      | a
            +---
    
    class B1        size(12):
            +---
     0      | {vbptr}
     4      | b1
            +---
            +--- (virtual base A)
     8      | a
            +---
    
    class B2        size(12):
            +---
     0      | {vbptr}
     4      | b2
            +---
            +--- (virtual base A)
     8      | a
            +---
    
    class C size(24):
            +---
            | +--- (base class B1)
     0      | | {vbptr}
     4      | | b1
            | +---
            | +--- (base class B2)
     8      | | {vbptr}
    12      | | b2
            | +---
    16      | c
            +---
            +--- (virtual base A)
    20      | a
            +---
    

    and the following vtables:

    B1::$vbtable@:
     0      | 0
     1      | 8 (B1d(B1+0)A)
    
    B2::$vbtable@:
     0      | 0
     1      | 8 (B2d(B2+0)A)
    
    C::$vbtable@B1@:
     0      | 0
     1      | 20 (Cd(B1+0)A)
    
    C::$vbtable@B2@:
     0      | 0
     1      | 12 (Cd(B2+0)A)
    

    Note that the offsets are relative to the address of the vtable, and note that for the two vtables generated for the B1 and B2 subobjects of C, the offsets are different.

    (Also note that this is entirely an implementation detail--other compilers may implement virtual functions and bases differently. This example demonstrates one way that they are implemented, and they are very commonly implemented this way.)