The Mechanism of Virtual Inheritance and the Implementation of Polymorphism through Virtual Functions in C++

The Mechanism of Virtual Inheritance

Virtual inheritance is designed to solve the diamond inheritance problem. Here is a code example:

class Furniture {
public:
    int _weight;
};

class Sofa : virtual public Furniture {
public:
    int _a;
};

class Bed : virtual public Furniture {
public:
    int _b;
};

class SofaBed : public Sofa, public Bed {
public:
    int _c;
};

int main() {
    SofaBed sb;
    sb._weight = 100;
    sb._a = 1;
    sb._b = 2;
    sb._c = 3;
    return 0;
}

In virtual inheritance, each derived class that uses virtual inheritance adds a virtual base class pointer (vbptr), which is located at the top of the derived class object. The vbptr pointer points to a virtual base class table (vbtable) that does not occupy object memory. The virtual base class table records the offsets of the base class member variables relative to the vbptr pointer, allowing the base class member variables to be located based on these offsets.

When a derived class of a virtual base class is inherited as a base class, the virtual base class pointer (vbptr) is also inherited. Therefore, the arrangement of member variables in the underlying derived class object differs from that in normal inheritance.

The Mechanism of Virtual Inheritance and the Implementation of Polymorphism through Virtual Functions in C++

The above image shows the logical storage structure of the object sbed: at the top of the object sbed is the virtual base class pointer and member variable of the base class Sofa; immediately following is the virtual base class pointer and member variable of the base class Bed. The member variable of the indirect base class Furniture has only one copy in the object sbed, located at the bottom. The virtual base class pointer Sofa::vbptr points to the virtual base class table of the Sofa class, which records the distance from _wood to Sofa::vbptr as 16 bytes; similarly, the virtual base class table of the Bed class records the distance from _wood to Bed::vbptr as 8 bytes. The offsets allow for quick access to the base class member variables.

Do Offsets in the Virtual Base Class Table Change During Inheritance?

In virtual inheritance, the offsets in the virtual base class table are shared among derived classes and do not change due to the inheritance process.

The main purpose of virtual inheritance is to solve the diamond inheritance problem, which occurs when a derived class is derived from two or more common base classes that share a common base class, potentially leading to multiple inheritances of the same common base class. To address this issue, C++ introduced the concept of virtual base classes, where the virtual base class table maintains offset information to ensure that there is only one instance of the common base class in the derived classes.

Since the offsets in the virtual base class table are shared within the class hierarchy, they do not change due to derivation. This ensures that regardless of how many times a class is derived, the position and offset of the virtual base class within the derived class remain consistent, thereby ensuring the uniqueness and correctness of the shared base class.

Construction of Shared Base Classes Occurs Only Once

The virtual base class table stores the construction flag information for the shared base class. When an object of a derived class is created, the constructor of the virtual base class checks the construction flag information in the virtual base class table to determine whether construction is necessary, ensuring that the construction of the shared base class occurs only once.

The Mechanism of Polymorphism Implemented by Virtual Functions

Virtual functions achieve polymorphism through dynamic binding. When the compiler encounters the keyword virtual during compilation, it does not bind the function call but instead creates a virtual function table (Vtable) for the class containing the virtual function. In the virtual function table, the compiler saves the addresses of the virtual functions in the order of their declarations. Additionally, the compiler adds a hidden virtual function pointer (VPTR) in the class, pointing to the virtual function table. When an object is created, the virtual function pointer (VPTR) is placed at the start of the object, allocating space for it and initializing it to the address of the virtual function table. It is important to note that the virtual function table does not occupy object space.

When a derived class inherits from a base class, it also inherits the base class’s virtual function pointer. When creating a derived class object, the virtual function pointer in the derived class object points to its own virtual function table. In the derived class’s virtual function table, the derived class’s virtual functions will override the base class’s virtual functions with the same name. When operating on a derived class object through a base class pointer or reference, the compiler retrieves the virtual function pointer from the object’s memory, finds the virtual function table through the pointer, and calls the corresponding virtual function.

Code example:

class Base1       // Define base class Base1
{
public:
 virtual void func();    // Declare virtual function func()
 virtual void base1();    // Declare virtual function base1()
 virtual void show1();    // Declare virtual function show1()
};

class Base2       // Define base class Base2
{
public:
 virtual void func();    // Declare virtual function func()
 virtual void base2();    // Declare virtual function base2()
 virtual void show2();    // Declare virtual function show2()
};

// Define Derive class, publicly inheriting Base1 and Base2
class Derive :public Base1, public Base2
{
public:
 virtual void func();    // Declare virtual function func()
 virtual void base1();    // Declare virtual function base1()
 virtual void show2();    // Declare virtual function show2()
};

The inheritance relationship of the above code is illustrated in the following image:

The Mechanism of Virtual Inheritance and the Implementation of Polymorphism through Virtual Functions in C++

During compilation, the compiler detects that both Base1 and Base2 classes have virtual functions, creating separate virtual function tables for both classes and adding virtual function pointers in each class.

If objects of Base1 class (e.g., base1) and Base2 class (e.g., base2) are created, the virtual function pointers in these objects will be initialized to the addresses of their respective virtual function tables, meaning the virtual function pointers point to the virtual function tables. The following image illustrates this:

The Mechanism of Virtual Inheritance and the Implementation of Polymorphism through Virtual Functions in C++

The Derive class inherits the virtual function pointers from both Base1 and Base2 classes. The virtual functions func(), base1(), and show2() in the Derive class will override the base class’s virtual functions with the same names. If an object of the Derive class (e.g., derive) is created, the logical memory structure of the object derive is illustrated in the following image:

The Mechanism of Virtual Inheritance and the Implementation of Polymorphism through Virtual Functions in C++

When operating on the Derive class object through pointers or references of Base1 and Base2 classes, at runtime, the compiler retrieves the virtual function pointer from the memory of the Derive class object, finds the virtual function table through the pointer, and calls the corresponding virtual function. Different classes have different implementations of their functions, achieving polymorphism during the call.

Leave a Comment