1. Overview
Simply put, each class containing a virtual function (whether it is itself or inherited) has at least one virtual function table corresponding to it, which holds the corresponding function pointers for all the virtual functions of the class. Cases:
which
- The virtual function Table of B holds the B::foo and b::bar two function pointers.
- The virtual function table of D is stored in the virtual function B::foo, which inherits from B, and overrides the D::bar of the virtual function B::bar of the base class, and the new virtual function D::quz.
Tip: For the sake of description, this article ignores the effect of memory alignment on the layout when exploring the object memory layout.
2. virtual function Table construction process
From the compiler's point of view, the virtual function table of B is very well constructed, and the virtual function table construction of D is relatively complex. Here is a way to construct the virtual function table for D (for reference only):
Tip: The process is done by the compiler, so it can also be said that the virtual function substitution process occurs at compile time.
3. virtual function Call procedure
Take the following procedure as an example:
The compiler knows only that PB is a pointer to the b* type, and does not know the specific object type it points to: PB may point to the object of B, or it may point to the object of D.
But for "Pb->bar ()", the compile time can be determined: Another parameter here operator-> is B::bar (because PB is b* type, the compiler thinks bar is B::bar), and B::bar and D:: Bar is equal in the offset position of the respective virtual function table.
No matter what type of object the PB points to, as long as it can determine the offset value of the function in the virtual function, when it is run, it can determine the concrete type, and can find the corresponding vptr, can find out the function that should be called actually.
Hint: I have been mentioned in the article "C + +: In-depth understanding of data member pointers, function member pointers": The PTR portion of the virtual function pointer is the offset value in the virtual function table (in bytes) plus 1.
B::bar is a virtual function pointer whose PTR portion is 9, and it has an offset value of 8 (8+1=9) in the virtual function table of B.
When the program executes to "Pb->bar ()", it has been able to determine the specific type of PB point:
- If PB points to object B, you can get the vptr of the B object, plus the offset value 8 ((char*) vptr + 8) to find the B::bar.
- If PB points to the object of D, you can get the vptr of the D object, plus the offset value 8 ((char*) vptr + 8) to find the D::bar.
- If PB points to other types of objects ... Similarly...
4. Multiple inheritance
When a class inherits multiple classes, and multiple base classes have virtual functions, the subclass object will contain pointers to multiple virtual function tables (that is, multiple vptr), for example:
Where: the virtual function of D itself and the B-base class share the same virtual function table, so also called B is the main base class of D (primary base classes).
The virtual function substitution process is similar to the previous description, with a single virtual function table and a copy-and-replace process.
The call procedure for a virtual function is basically similar to the previous description, except that the position that the base pointer is pointing to may not be the starting position of the derived class object, as in the following program:
5. Diamond Inheritance
This article does not discuss the case of diamond inheritance, personally think: the complexity of diamond inheritance is much greater than its use value, which is also C + + let people love and hate one of the reasons.
For further research, you can refer to the Itanium C + + ABI.
C + + Notes: The basic principle of virtual function implementation