[C++] What is the difference between a virtual table and a virtual base table?

Virtual table and virtual base table

  • virtual table
  • virtual base table
  • Object model when both virtual inheritance and virtual functions exist

Virtual table

We know that if the method declared in the class is modified with virtual, it means that the current method should be used as a virtual function, and the storage of virtual functions is different from the storage of ordinary functions.
When there is a virtual function declaration, the compiler will create a virtual function table, put the current virtual function into the virtual function table in the order of declaration, and this virtual function table is actually an array of function pointers, and then put the current virtual function The address of the table is placed at the very beginning of the object model.

class A
{<!-- -->
public:
virtual void fun1(){<!-- -->cout << "A::fun1()" << endl;}
virtual void fun2(){<!-- -->cout << "A::fun2()" << endl;}
virtual void fun3(){<!-- -->cout << "A::fun3()" << endl;}
int _a;
};

Its corresponding object model is as follows:

So, in essence, the virtual function table is an array of function pointers, and the first address of the virtual function table is stored in the object model. When we need to call a virtual function, pass the corresponding object, and then we can get the address of the object through the address of the object. Virtual table pointer, so as to obtain the virtual table, and then obtain the address of a virtual function in the corresponding virtual function table, so as to call (know the entry address of the function, you can call the corresponding function)

Virtual base table

We know that when diamond inheritance occurs, there must be multiple base class object members in the object model.

//Common inheritance
class A
{<!-- -->
public:
int _a;
};
class B : public A
{<!-- -->
public:
int _b;
};
class C : public A
{<!-- -->
public:
int _c;
};
class D : public B, public C
{<!-- -->
public:
int _d;
};

In the above code, there must be a member of class B and class C in the object of class D that inherits from the member _a of class A object, so there are two members of _a, which leads to ambiguity when accessing _a, and then As the depth and breadth of inheritance increase, object members become more and more redundant.
To solve this problem, virtual inheritance emerged.

//Diamond virtual inheritance
class A
{<!-- -->
public:
int _a;
};
class B : virtual public A
{<!-- -->
public:
int _b;
};
class C : virtual public A
{<!-- -->
public:
int _c;
};
class D : public B, public C
{<!-- -->
public:
int _d;
};

By letting Class B and Class C virtually inherit Class A, the object model changes from Figure 1 to Figure 2

Such a change makes Class B and Class C inherit Class A, but Class B and Class C do not store objects of Class A (there is only one copy of the base class object, which is stored at the end of the entire object model), except In addition to the addition of subclasses, there is only one pointer, and this pointer is called the virtual base table pointer.

The virtual base table pointer points to a virtual base table. For the virtual base table of class B ptr1, the total size is 8 bytes (under 32-bit system), and the first 4 bytes store the subclass object relative to itself The offset of the starting position (at present, it is 0, when there is virtual inheritance of virtual functions, it is not 0), and the last 4 bytes are stored as the offset of the subclass object relative to the base class part.

ptr2 points to the virtual base table of the object of class C, the total size is 8 bytes (under 32-bit system), the first 4 bytes store the offset of the subclass object relative to its own starting position, (currently Look at 0, when there is virtual inheritance of virtual functions, it is not 0), the last 4 bytes are stored as the offset of the subclass object relative to the base class part.

It can be found that only one virtual table is stored in the entire class object, that is to say, different objects of a class share the same virtual table. The virtual base table has multiple copies, depending on whether the current class virtually inherits the base class. If the base class is virtually inherited, a virtual base table pointer will be created to point to a virtual base table.

Object model when both virtual inheritance and virtual functions exist

Then there is another problem. When virtual inheritance and virtual functions appear in the inheritance system at the same time, what does the object model look like?

class A
{<!-- -->
public:
virtual void fun1()
{<!-- -->
cout << "A::fun1()" << endl;
}
virtual void fun2()
{<!-- -->
cout << "A::fun2()" << endl;
}
int _a;
}
class B : virtual public A
{<!-- -->
public:
virtual void fun1()
{<!-- -->
cout << "B::fun1()" << endl;
}
virtual void fun3()
{<!-- -->
cout << "B::fun3()" << endl;
}
int _b;
};
class C : virtual public A
{<!-- -->
public:
virtual void fun2()
{<!-- -->
cout << "C::fun2()" << endl;
}
virtual void fun4()
{<!-- -->
cout << "C::fun4()" << endl;
}
int _c;
};
class D : public B, public C
{<!-- -->
public:
virtual void fun1()
{<!-- -->
cout << "D::fun1()" << endl;
}
virtual void fun2()
{<!-- -->
cout << "D::fun2()" << endl;
}
virtual void fun5()
{<!-- -->
cout << "D::fun5()" << endl;
}
int _d;
}

In the above code, both class B and class C inherit from class A, and the virtual functions in class A are rewritten, and new virtual functions are also added. Class D inherits classes B and C, rewrites the virtual functions in classes B and C, and adds new virtual functions at the same time.

So what does the object model look like under this inheritance system at present?
In fact, it is not difficult to imagine that since both class B and class C are virtual inheritance, only one member of class A will be kept at the bottom, and at the same time, class B and class C will save their own virtual base table pointers, while class D is an ordinary Inheritance, in order, the newly added virtual functions are placed in the virtual table of class B.

We found by taking the address that the object model is indeed the above, but between the D class and the A class, put 00000000 as the object division distinction (guess)

Please note: The current verification situation is verified in vs2019.

Summary: When virtual base tables and virtual tables exist at the same time (virtual inheritance and virtual functions exist at the same time), the object model is still the same as virtual inheritance on the whole (the order of base class objects is arranged from top to bottom in the order of declaration, and the object model There are no members of the grandparent class, and the members of the grandparent class are placed at the bottom of the model). However, due to the existence of virtual functions, the virtual functions that class B rewrites the virtual functions of class A are directly modified in class A, and the new virtual functions of class B are placed in the virtual table inside class B, and class C The virtual function rewritten by the virtual function of class A is directly modified in class A, and the new virtual function of class C is placed in the virtual table inside class C. The virtual functions rewritten by class D to class B and class C are directly modified in the corresponding class, and the new ones of class D are directly placed in the virtual table of class B.

Through the above description, it can be known that the virtual functions stored in the virtual tables of class B, class C and class A are:



For the virtual base table, it represents the offset of the subclass object relative to its own starting position. If it is class B, the starting position of the class B object already has a virtual table pointer, then in the virtual base table The first four bytes need to be -4 to represent the offset relative to its own starting position, and the last four bytes are the offset relative to the base class, which is a normal calculation method.

For the virtual base table of class B and class C, the values are: