Foreword
The previous chapter briefly described the calling link of virtual functions. This chapter mainly explores the polymorphic characteristics of class objects with various inheritance relationships in C++.
- In-depth exploration of C++ polymorphism ① – Virtual function call link
- A Deeper Exploration of C++ Polymorphism ② – Inheritance
- A Deeper Exploration of C++ Polymorphism ③ – Virtual Destruction
1. Overview
Encapsulation, inheritance, and polymorphism are the three major features of C++, among which polymorphism is closely related to inheritance. The C++ language supports three inheritance relationships: single inheritance, multiple inheritance, and virtual inheritance:
Image source: “Polytype and Virtuality”
2. Inheritance relationship
2.1. Single inheritance
Single inheritance in C++ means that a class can only inherit properties and methods from one parent class.
Text source: ChatGPT
The single inheritance object class hierarchy of dynamic polymorphism is relatively simple:
- There is only one virtual pointer in the object memory, and it is at the first place.
- The virtual functions on the virtual table are covered layer by layer, and finally the virtual function table corresponding to the object is obtained. See the figure below for details.
- Test the code.
/* g + + -O0 -std=c + + 11 -fdump-class-hierarchy test.cpp -o test */ #include <iostream> class Base {<!-- --> public: virtual void vBaseFunc() {<!-- -->} virtual void vBaseFunc2() {<!-- -->} virtual void vBaseFunc3() {<!-- -->} long long m_base_data; long long m_base_data2; }; class Base2 : public Base {<!-- --> public: virtual void vBaseFunc() {<!-- -->} virtual void vBase2Func() {<!-- --> std::cout << "Base2::vBase2Func" << std::endl; } virtual void vBase2Func2() {<!-- -->} long long m_base2_data; long long m_base2_data2; }; class Derived : public Base2 {<!-- --> public: virtual void vBaseFunc2() {<!-- -->} virtual void vBase2Func() {<!-- --> std::cout << "Derived::vBase2Func" << std::endl; } virtual void vDerivedFunc() {<!-- -->} virtual void vDerivedFunc2() {<!-- -->} long long m_derived_data; long long m_derived_data2; };
- Class layout hierarchy.
Vtable for Base # _ZTV4Base: vtable for Base Base::_ZTV4Base: 5u entries 0 (int (*)(...))0 # _ZTI4Base: typeinfo for Base 8 (int (*)(...))( & amp; _ZTI4Base) 16 (int (*)(...))Base::vBaseFunc 24 (int (*)(...))Base::vBaseFunc2 32 (int (*)(...))Base::vBaseFunc3 Vtable for Base2 Base2::_ZTV5Base2: 7u entries 0 (int (*)(...))0 8 (int (*)(...))( & amp; _ZTI5Base2) 16 (int (*)(...))Base2::vBaseFunc 24 (int (*)(...))Base::vBaseFunc2 32 (int (*)(...))Base::vBaseFunc3 40 (int (*)(...))Base2::vBase2Func 48 (int (*)(...))Base2::vBase2Func2 # Derived virtual table. Vtable for Derived # _ZTV7Derived: vtable for Derived Derived::_ZTV7Derived: 9u entries 0 (int (*)(...))0 # _ZTI7Derived: typeinfo for Derived 8 (int (*)(...))( & amp; _ZTI7Derived) 16 (int (*)(...))Base2::vBaseFunc 24 (int (*)(...))Derived::vBaseFunc2 32 (int (*)(...))Base::vBaseFunc3 40 (int (*)(...))Derived::vBase2Func 48 (int (*)(...))Base2::vBase2Func2 56 (int (*)(...))Derived::vDerivedFunc 64 (int (*)(...))Derived::vDerivedFunc2 #Inheritance relationship of classes Class Derived size=56 align=8 base size=56 base align=8 Derived (0x0x7fb058fa8478) 0 # The virtual pointer points to the location of the virtual table. vptr=(( & amp; Derived::_ZTV7Derived) + 16u) Base2 (0x0x7fb058fa8a28) 0 primary-for Derived (0x0x7fb058fa8478) Base (0x0x7fb058ee87e0) 0 primary-for Base2 (0x0x7fb058fa8a28)
- Virtual table integration.
- The overall layout of the object.
-
Virtual function call.
- The first thing saved in the object is the virtual pointer vptr, which points to the virtual table.
- The virtual table address pointed to by the virtual pointer is offset 0x18 bytes to the high address, so that the Derived::vBase2Func virtual function address can be obtained and then called.
int main() {<!-- --> auto d = new Derived; std::cout << d << std::endl; auto b = static_cast<Base2 *>(d); std::cout << b << std::endl; b->vBase2Func(); return 0; } // Output: // 0x13a0010 // 0x13a0010 // Derived::vBase2Func
2.2. Multiple inheritance
C++ supports multiple inheritance, which means that a class can inherit properties and methods from multiple parent classes. In C++, multiple parent classes can be specified using comma separation.
Text source: ChatGPT.
- Test the code.
/* g + + -O0 -std=c + + 11 -fdump-class-hierarchy test.cpp -o test */ #include <iostream> class Base {<!-- --> public: virtual void vBaseFunc() {<!-- -->} virtual void vBaseFunc2() {<!-- -->} long long m_base_data; long long m_base_data2; }; class Base2 {<!-- --> public: virtual void vBase2Func() {<!-- -->} virtual void vBase2Func2() {<!-- --> std::cout << "Base2::vBase2Func2" << std::endl; } long long m_base2_data; long long m_base2_data2; }; class Base3 {<!-- --> public: virtual void vBase3Func() {<!-- -->} virtual void vBase3Func2() {<!-- -->} long long m_base3_data; long long m_base3_data2; }; class Derived : public Base, public Base2, public Base3 {<!-- --> public: virtual void vBaseFunc() {<!-- -->} virtual void vBase2Func2() {<!-- --> std::cout << "Derived::vBase2Func2" << std::endl; } virtual void vBase3Func2() {<!-- -->} virtual void vDerivedFunc() {<!-- -->} virtual void vDerivedFunc2() {<!-- -->} long long m_derived_data; long long m_derived_data2; };
- Class layout hierarchy.
Vtable for Base # _ZTV4Base: vtable for Base Base::_ZTV4Base: 4u entries 0 (int (*)(...))0 # _ZTI4Base: typeinfo for Base 8 (int (*)(...))( & amp; _ZTI4Base) 16 (int (*)(...))Base::vBaseFunc 24 (int (*)(...))Base::vBaseFunc2 Vtable for Base2 # _ZTV5Base2: vtable for Base2 Base2::_ZTV5Base2: 4u entries 0 (int (*)(...))0 # _ZTI5Base2: typeinfo for Base2 8 (int (*)(...))( & amp; _ZTI5Base2) 16 (int (*)(...))Base2::vBase2Func 24 (int (*)(...))Base2::vBase2Func2 Vtable for Base3 # _ZTV5Base3: vtable for Base3 Base3::_ZTV5Base3: 4u entries 0 (int (*)(...))0 # _ZTI5Base3: typeinfo for Base3 8 (int (*)(...))( & amp; _ZTI5Base3) 16 (int (*)(...))Base3::vBase3Func 24 (int (*)(...))Base3::vBase3Func2 Vtable for Derived # _ZTV7Derived: vtable for Derived Derived::_ZTV7Derived: 16u entries 0 (int (*)(...))0 # _ZTI7Derived: typeinfo for Derived 8 (int (*)(...))( & amp; _ZTI7Derived) 16 (int (*)(...))Derived::vBaseFunc 24 (int (*)(...))Base::vBaseFunc2 32 (int (*)(...))Derived::vBase2Func2 40 (int (*)(...))Derived::vBase3Func2 48 (int (*)(...))Derived::vDerivedFunc 56 (int (*)(...))Derived::vDerivedFunc2 64 (int (*)(...))-24 72 (int (*)(...))( & amp; _ZTI7Derived) 80 (int (*)(...))Base2::vBase2Func # _ZThn24_N7Derived11vBase2Func2Ev: non-virtual thunk to Derived::vBase2Func2() 88 (int (*)(...))Derived::_ZThn24_N7Derived11vBase2Func2Ev 96 (int (*)(...))-48 104 (int (*)(...))( & amp; _ZTI7Derived) 112 (int (*)(...))Base3::vBase3Func # _ZThn48_N7Derived11vBase3Func2Ev: non-virtual thunk to Derived::vBase3Func2() 120 (int (*)(...))Derived::_ZThn48_N7Derived11vBase3Func2Ev Class Derived size=88 align=8 base size=88 base align=8 Derived (0x0x7f4196042348) 0 vptr=(( & amp; Derived::_ZTV7Derived) + 16u) Base (0x0x7f4195f3e840) 0 primary-for Derived (0x0x7f4196042348) Base2 (0x0x7f4195f3e8a0) 24 vptr=(( & amp; Derived::_ZTV7Derived) + 80u) Base3 (0x0x7f4195f3e900) 48 vptr=(( & amp; Derived::_ZTV7Derived) + 112u)
-
Virtual table integration.
- First, the virtual table of the derived class is combined with the virtual table of the first base class to form a virtual table unit, and the virtual function of the base class is overridden.
- Other base classes serve as an independent virtual table unit. When a derived class virtual function overrides a base class virtual function, the base class corresponds to the virtual function and jumps to the corresponding virtual function of the first virtual table unit through thunk technology.
-
The overall layout of the object. As can be seen from the picture below:
- Multiple inheritance has multiple virtual pointers and points to corresponding virtual table units.
- If a derived class has N multiple inheritance from a single base class, then its objects have N virtual pointers and virtual table locations.
- Virtual function call. With the above understanding of memory layout, it should not be difficult for us to understand how the following base class pointer calls the derived class virtual function:
int main() {<!-- --> auto d = new Derived; std::cout << d << std::endl; auto b = static_cast<Base2 *>(d); std::cout << b << std::endl; b->vBase2Func2(); return 0; } // Output: // 0x13db010 // 0x13db028 // Derived::vBase2Func2
- The Base2 pointer points to the address where vptr2 is stored: offset 0x18 bytes from the top of object memory to the high address to obtain the vptr2 virtual pointer.
- The virtual table address pointed by the vptr2 pointer is offset 0x8 bytes to the high address, and the non-virtual thunk to Derived::vBase2Func2() address is obtained.
- Jump to the Derived::vBase2Func2 virtual function through the non-virtual thunk to Derived::vBase2Func2() address, and obtain the corresponding virtual function address on the virtual table for calling.
- Understand how the function thunk to jump works through assembly.
# thunk to jump principle (assembly). 0000000000400aba <non-virtual thunk to Derived::vBase2Func2()>: # The rdi register stores the address pointed to by the b pointer, which is offset by 0x18 bytes toward the lower address. # That is, the rdi register stores the first address of Derived memory. # In other words, pass Derived's this pointer as a parameter to the Derived::vBase2Func2 function. 400aba: 48 83 ef 18 sub $0x18,%rdi # Call the Derived::vBase2Func2() function. 400abe: eb d0 jmp 400a90 <Derived::vBase2Func2()>
- Think about it, is the polymorphic instance object of multiple inheritance above released in the following way correct? ! (For details, please refer to: Virtual Destruction).
int main() {<!-- --> Base2* b = new Derived; delete b; return 0; }
2.3. Virtual inheritance
Multiple inheritance allows a class to have the characteristics of multiple different parent classes, but it may also cause some problems, such as the diamond inheritance problem. To solve this problem, C++ provides the concepts of virtual inheritance and virtual base classes. Virtual inheritance solves the diamond inheritance problem by ensuring that there is only one instance of a shared base class.
In C++, virtual inheritance is a special type of inheritance. It is used to solve the diamond inheritance problem in multiple inheritance. When a class inherits from multiple base classes through virtual inheritance, only one instance of the base class is retained and inheritance is not repeated. This avoids the ambiguity and redundancy caused by diamond inheritance. In virtual inheritance, the derived class needs to declare the base class using the keyword “virtual”.
Text source: ChatGPT
Because there is a shared base class in the inheritance relationship, in order to avoid the shared base class generating multiple object copies and wasting memory, the memory layout of virtual inheritance will also be different from single inheritance and multiple inheritance:
- The member data of the public base class is stored at the bottom of the object memory.
- Virtual inheritance introduces VTT (Virtual Table Table) to construct a virtual table.
- The virtual table prefix introduces the vbase_offset offset: the memory location offset between the current virtual table and the common base class virtual table.
The class hierarchy structure of virtual inheritance is a bit complicated. Interested friends can refer to: What is the VTT for a class.
2.3.1. Overall layout of objects
- Test the code.
/* g + + -O0 -std=c + + 11 -fdump-class-hierarchy test.cpp -o test */ #include <iostream> class Base {<!-- --> public: virtual void vBaseFunc() {<!-- -->} virtual void vBaseFunc2() {<!-- -->} long long m_base_data = 0x11; long long m_base_data2 = 0x12; }; class Base2 : virtual public Base {<!-- --> public: virtual void vBaseFunc() {<!-- -->} virtual void vBase2Func() {<!-- -->} virtual void vBase2Func2() {<!-- -->} long long m_base2_data = 0x21; long long m_base2_data2 = 0x22; }; class Base3 : virtual public Base {<!-- --> public: virtual void vBaseFunc2() {<!-- -->} virtual void vBase3Func() {<!-- -->} virtual void vBase3Func2() {<!-- --> std::cout << "Base3::vBase3Func2" << std::endl; } long long m_base3_data = 0x31; long long m_base3_data2 = 0x32; }; class Derived : public Base2, public Base3 {<!-- --> public: virtual void vBase2Func() {<!-- -->} virtual void vBase3Func2() {<!-- --> std::cout << "Derived::vBase3Func2" << std::endl; } virtual void vDerivedFunc() {<!-- -->} virtual void vDerivedFunc2() {<!-- -->} long long m_derived_data = 0x41; long long m_derived_data2 = 0x42; };
- Class object memory layout.
Vtable for Derived # _ZTV7Derived: vtable for Derived Derived::_ZTV7Derived: 21u entries 0 64u 8 (int (*)(...))0 # _ZTI7Derived: typeinfo for Derived 16 (int (*)(...))( & amp; _ZTI7Derived) 24 (int (*)(...))Base2::vBaseFunc 32 (int (*)(...))Derived::vBase2Func 40 (int (*)(...))Base2::vBase2Func2 48 (int (*)(...))Derived::vBase3Func2 56 (int (*)(...))Derived::vDerivedFunc 64 (int (*)(...))Derived::vDerivedFunc2 72 40u 80 (int (*)(...))-24 88 (int (*)(...))( & amp; _ZTI7Derived) 96 (int (*)(...))Base3::vBaseFunc2 104 (int (*)(...))Base3::vBase3Func # _ZThn24_N7Derived11vBase3Func2Ev: non-virtual thunk to Derived::vBase3Func2() 112 (int (*)(...))Derived::_ZThn24_N7Derived11vBase3Func2Ev 120 18446744073709551576u # -40 128 18446744073709551552u # -64 136 (int (*)(...))-64 144 (int (*)(...))( & amp; _ZTI7Derived) # _ZTv0_n24_N5Base29vBaseFuncEv: virtual thunk to Base2::vBaseFunc() 152 (int (*)(...))Base2::_ZTv0_n24_N5Base29vBaseFuncEv # _ZTv0_n32_N5Base310vBaseFunc2Ev: virtual thunk to Base3::vBaseFunc2() 160 (int (*)(...))Base3::_ZTv0_n32_N5Base310vBaseFunc2Ev Construction vtable for Base2 (0x0x7fd19d6aea90 instance) in Derived # _ZTC7Derived0_5Base2: construction vtable for Base2-in-Derived Derived::_ZTC7Derived0_5Base2: 12u entries 0 64u 8 (int (*)(...))0 # _ZTI5Base2: typeinfo for Base2 16 (int (*)(...))( & amp; _ZTI5Base2) 24 (int (*)(...))Base2::vBaseFunc 32 (int (*)(...))Base2::vBase2Func 40 (int (*)(...))Base2::vBase2Func2 48 0u 56 18446744073709551552u # -64 64 (int (*)(...))-64 # _ZTI5Base2: typeinfo for Base2 72 (int (*)(...))( & amp; _ZTI5Base2) # _ZTv0_n24_N5Base29vBaseFuncEv: virtual thunk to Base2::vBaseFunc() 80 (int (*)(...))Base2::_ZTv0_n24_N5Base29vBaseFuncEv 88 (int (*)(...))Base::vBaseFunc2 Construction vtable for Base3 (0x0x7fd19d6aeaf8 instance) in Derived Derived::_ZTC7Derived24_5Base3: 12u entries 0 40u 8 (int (*)(...))0 # _ZTI5Base3: typeinfo for Base3 16 (int (*)(...))( & amp; _ZTI5Base3) 24 (int (*)(...))Base3::vBaseFunc2 32 (int (*)(...))Base3::vBase3Func 40 (int (*)(...))Base3::vBase3Func2 48 18446744073709551576u # -40 56 0u 64 (int (*)(...))-40 # _ZTI5Base3: typeinfo for Base3 72 (int (*)(...))( & amp; _ZTI5Base3) 80 (int (*)(...))Base::vBaseFunc # _ZTv0_n32_N5Base310vBaseFunc2Ev: virtual thunk to Base3::vBaseFunc2() 88 (int (*)(...))Base3::_ZTv0_n32_N5Base310vBaseFunc2Ev VTT for Derived # _ZTV7Derived: vtable for Derived Derived::_ZTT7Derived: 7u entries 0 (( & amp; Derived::_ZTV7Derived) + 24u) # _ZTC7Derived0_5Base2: construction vtable for Base2-in-Derived 8 (( & amp; Derived::_ZTC7Derived0_5Base2) + 24u) 16 (( & amp; Derived::_ZTC7Derived0_5Base2) + 80u) # _ZTC7Derived24_5Base3: construction vtable for Base3-in-Derived 24 (( & amp; Derived::_ZTC7Derived24_5Base3) + 24u) 32 (( & amp; Derived::_ZTC7Derived24_5Base3) + 80u) 40 (( & amp; Derived::_ZTV7Derived) + 152u) 48 (( & amp; Derived::_ZTV7Derived) + 96u) Class Derived size=88 align=8 base size=64 base align=8 Derived (0x0x7fd19d7401c0) 0 vptridx=0u vptr=(( & amp; Derived::_ZTV7Derived) + 24u) Base2 (0x0x7fd19d6aea90) 0 primary-for Derived (0x0x7fd19d7401c0) subvttidx=8u Base (0x0x7fd19d5ee840) 64 virtual vptridx=40u vbaseoffset=-24 vptr=(( & amp; Derived::_ZTV7Derived) + 152u) Base3 (0x0x7fd19d6aeaf8) 24 subvttidx=24u vptridx=48u vptr=(( & amp; Derived::_ZTV7Derived) + 96u) Base (0x0x7fd19d5ee840) alternative-path
2.3.2. Construction sequence
We can understand through the construction sequence of the class: how the object memory layout is constructed step by step. When constructing the derived class Derived, construct the base class first, and then construct itself after the base class is constructed.
- Construction process.
|-- main |-- ... |-- Derived::Derived() |-- Base::Base() |-- Base2::Base2() |-- Base3::Base3()
- Construction process (assembly).
... 0x400b33: e8 34 02 00 00 callq 0x400d6c <Derived::Derived()> ... 0x400d83: e8 06 ff ff ff callq 400c8e <Base::Base()> ... 0x400d97: e8 20 ff ff ff callq 400cbc <Base2::Base2()> ... 0x400daf: e8 60 ff ff ff callq 400d14 <Base3::Base3()>
- Construct Base.
Vtable for Base # _ZTV4Base: vtable for Base Base::_ZTV4Base: 4u entries 0 (int (*)(...))0 # _ZTI4Base: typeinfo for Base 8 (int (*)(...))( & amp; _ZTI4Base) 16 (int (*)(...))Base::vBaseFunc 24 (int (*)(...))Base::vBaseFunc2 Class Base size=24 align=8 base size=24 base align=8 Base (0x0x7fd19d5ee720) 0 vptr=(( & amp; Base::_ZTV4Base) + 16u)
- Construct Base2.
Construction vtable for Base2 (0x0x7fd19d6aea90 instance) in Derived # _ZTC7Derived0_5Base2: construction vtable for Base2-in-Derived Derived::_ZTC7Derived0_5Base2: 12u entries 0 64u 8 (int (*)(...))0 # _ZTI5Base2: typeinfo for Base2 16 (int (*)(...))( & amp; _ZTI5Base2) 24 (int (*)(...))Base2::vBaseFunc 32 (int (*)(...))Base2::vBase2Func 40 (int (*)(...))Base2::vBase2Func2 48 0u 56 18446744073709551552u # -64 64 (int (*)(...))-64 # _ZTI5Base2: typeinfo for Base2 72 (int (*)(...))( & amp; _ZTI5Base2) # _ZTv0_n24_N5Base29vBaseFuncEv: virtual thunk to Base2::vBaseFunc() 80 (int (*)(...))Base2::_ZTv0_n24_N5Base29vBaseFuncEv 88 (int (*)(...))Base::vBaseFunc2 VTT for Derived # _ZTV7Derived: vtable for Derived Derived::_ZTT7Derived: 7u entries 0 (( & amp; Derived::_ZTV7Derived) + 24u) # _ZTC7Derived0_5Base2: construction vtable for Base2-in-Derived 8 (( & amp; Derived::_ZTC7Derived0_5Base2) + 24u) 16 (( & amp; Derived::_ZTC7Derived0_5Base2) + 80u) # _ZTC7Derived24_5Base3: construction vtable for Base3-in-Derived 24 (( & amp; Derived::_ZTC7Derived24_5Base3) + 24u) 32 (( & amp; Derived::_ZTC7Derived24_5Base3) + 80u) 40 (( & amp; Derived::_ZTV7Derived) + 152u) 48 (( & amp; Derived::_ZTV7Derived) + 96u)
- Construct Base3.
VTT for Derived # _ZTV7Derived: vtable for Derived Derived::_ZTT7Derived: 7u entries 0 (( & amp; Derived::_ZTV7Derived) + 24u) # _ZTC7Derived0_5Base2: construction vtable for Base2-in-Derived 8 (( & amp; Derived::_ZTC7Derived0_5Base2) + 24u) 16 (( & amp; Derived::_ZTC7Derived0_5Base2) + 80u) # _ZTC7Derived24_5Base3: construction vtable for Base3-in-Derived 24 (( & amp; Derived::_ZTC7Derived24_5Base3) + 24u) 32 (( & amp; Derived::_ZTC7Derived24_5Base3) + 80u) 40 (( & amp; Derived::_ZTV7Derived) + 152u) 48 (( & amp; Derived::_ZTV7Derived) + 96u) Construction vtable for Base3 (0x0x7fd19d6aeaf8 instance) in Derived # _ZTC7Derived24_5Base3: construction vtable for Base3-in-Derived Derived::_ZTC7Derived24_5Base3: 12u entries 0 40u 8 (int (*)(...))0 # _ZTI5Base3: typeinfo for Base3 16 (int (*)(...))( & amp; _ZTI5Base3) 24 (int (*)(...))Base3::vBaseFunc2 32 (int (*)(...))Base3::vBase3Func 40 (int (*)(...))Base3::vBase3Func2 48 18446744073709551576u # -40 56 0u 64 (int (*)(...))-40 # _ZTI5Base3: typeinfo for Base3 72 (int (*)(...))( & amp; _ZTI5Base3) 80 (int (*)(...))Base::vBaseFunc # _ZTv0_n32_N5Base310vBaseFunc2Ev: virtual thunk to Base3::vBaseFunc2() 88 (int (*)(...))Base3::_ZTv0_n32_N5Base310vBaseFunc2Ev
- Construct Derived (see Overall Layout above).
2.3.3. Virtual function call
int main() {<!-- --> auto d = new Derived; std::cout << d << std::endl; auto b = static_cast<Base3 *>(d); std::cout << b << std::endl; b->vBase3Func2(); return 0; } // Output: // 0x9fa010 // 0x9fa028 // Derived::vBase3Func2
- b The pointer points to the address where vptr.base3 is stored: offset 0x18 bytes from the top of Derived object memory.
- The virtual table address pointed to by the vptr.base3 pointer is offset 0x10 bytes to the high address, and the non-virtual thunk to Derived::vBase3Func2() function address is obtained.
- Jump to the Derived::vBase3Func2 virtual function through the non-virtual thunk to Derived::vBase3Func2() address, and obtain the corresponding virtual function on the virtual table for calling.
3. Postscript
-
To understand the polymorphic object memory layout, you must pay attention to understanding how (multiple) virtual pointers are offset according to different base class pointers. When the virtual pointer points to the virtual table, to obtain the corresponding virtual function, the virtual pointer must be offset. Only by moving a certain position can the virtual function on the corresponding virtual table be located.
-
If you want to use one word to describe polymorphism, it would be
overwriting
. Derived classes override base class virtual functions. Like layers, the upper layer (derived class) covers the lower layer (base class), layer by layer. , and finally got the overwritten result; this is also the core way of thinking for us to understand thevirtual table
structure. -
Regarding C++ polymorphic exploration with inheritance relationships, due to my limited level, the above only made some basic and simple Demo analysis, and some application scenarios were not covered (such as virtual destructor).
-
Many technical details are not mentioned in the article. Interested friends can write the demo and debug it with gdb, check the address data on the object memory layout, and disassemble to check whether the logic of the object construction is consistent with their own understanding. In this way Only in this way can we find the essence of the answer in the ever-changing appearance of the problem.