xxxxxxxxxx
551class A {
2public:
3 A() {
4 cout << "A's cons." << endl;
5 }
6
7 virtual ~A() {
8 cout << "A's des." << endl;
9 }
10
11 virtual void f() {
12 cout << "A's f()." << endl;
13 }
14
15 void g() {
16 f();
17 }
18};
19
20class B
21 : public A {
22public:
23 B() {
24 f();
25 cout << "B's cons." << endl;
26 }
27
28 ~B() {
29 cout << "B's des." << endl;
30 }
31};
32
33class C
34 : public B {
35public:
36 C() {
37 cout << "C's cons." << endl;
38 }
39
40 ~C() {
41 cout << "C's des." << endl;
42 }
43
44 void f() {
45 cout << "C's f()." << endl;
46 }
47};
48int main(void) {
49 A *pa = new C();
50 pa->g();
51
52 delete pa;
53
54 return 0;
55}
执行结果:
xxxxxxxxxx
81A's cons.
2A's f().
3B's cons.
4C's cons.
5C's f().
6A's des.
7B's des.
8C's des.
A *pa = new C();
继承链:A
← B
← C
构造顺序(从基类到派生类):
步骤1:A的构造函数执行
xxxxxxxxxx
31A() {
2 cout << "A's cons." << endl; // 输出:A's cons.
3}
步骤2:B的构造函数执行
xxxxxxxxxx
41B() {
2 f(); // 调用虚函数f()
3 cout << "B's cons." << endl;
4}
关键点:在B的构造函数中调用 f()
此时C的构造函数还未执行
虚函数指针指向B类的虚函数表
但B类没有重写 f()
,所以调用A的 f()
输出:A's f().
然后输出:B's cons.
步骤3:C的构造函数执行
xxxxxxxxxx
31C() {
2 cout << "C's cons." << endl; // 输出:C's cons.
3}
pa->g();
xxxxxxxxxx
31void g() {
2 f(); // 调用虚函数f()
3}
此时对象完全构造完成
虚函数指针指向C类的虚函数表
C类重写了 f()
函数
输出:C's f().
delete pa;
析构顺序(从派生类到基类):
xxxxxxxxxx
31~C() { cout << "C's des." << endl; } // 输出:C's des.
2~B() { cout << "B's des." << endl; } // 输出:B's des.
3virtual ~A() { cout << "A's des." << endl; } // 输出:A's des.
xxxxxxxxxx
101C对象的内存布局:
2+------------------+
3| vptr (虚函数表指针) | <-- 指向C的虚函数表
4+------------------+
5| A的数据成员 | (本例中A没有数据成员)
6+------------------+
7| B的数据成员 | (本例中B没有数据成员)
8+------------------+
9| C的数据成员 | (本例中C没有数据成员)
10+------------------+
A的虚函数表
xxxxxxxxxx
61A_vtable:
2+----------+
3| A::~A() | <-- 虚析构函数
4+----------+
5| A::f() | <-- 虚函数f()
6+----------+
B的虚函数表
xxxxxxxxxx
61B_vtable:
2+----------+
3| B::~B() | <-- 重写的析构函数
4+----------+
5| A::f() | <-- 继承A的f()(B没有重写)
6+----------+
C的虚函数表
xxxxxxxxxx
61C_vtable:
2+----------+
3| C::~C() | <-- 重写的析构函数
4+----------+
5| C::f() | <-- 重写的f()函数
6+----------+
xxxxxxxxxx
101// 构造过程中vptr的变化:
2
3// 1. A构造函数执行时
4C对象: vptr -> A_vtable
5
6// 2. B构造函数执行时
7C对象: vptr -> B_vtable // 此时调用f()会找到A::f()
8
9// 3. C构造函数执行完成后
10C对象: vptr -> C_vtable // 此时调用f()会找到C::f()
在对象的构造过程中,vptr(虚函数表指针)会指向不同的虚函数表。这是C++虚函数机制的一个重要特性。
当创建 C
对象时,构造函数的执行顺序是:A构造函数
→ B构造函数
→ C构造函数
xxxxxxxxxx
271// 假设我们有这样的继承关系
2class A {
3public:
4 A() {
5 // 此时 vptr -> A_vtable
6 cout << "A constructor" << endl;
7 }
8 virtual void f() { cout << "A::f()" << endl; }
9};
10
11class B : public A {
12public:
13 B() {
14 // 此时 vptr -> B_vtable
15 f(); // 调用的是 A::f(),因为B没有重写f()
16 cout << "B constructor" << endl;
17 }
18};
19
20class C : public B {
21public:
22 C() {
23 // 此时 vptr -> C_vtable
24 cout << "C constructor" << endl;
25 }
26 void f() override { cout << "C::f()" << endl; }
27};
阶段1:A构造函数执行时
xxxxxxxxxx
61C对象内存状态:
2+------------------+
3| vptr -> A_vtable | ← 指向A的虚函数表
4+------------------+
5| A的数据成员 |
6+------------------+
阶段2:B构造函数执行时
xxxxxxxxxx
81C对象内存状态:
2+------------------+
3| vptr -> B_vtable | ← 指向B的虚函数表
4+------------------+
5| A的数据成员 |
6+------------------+
7| B的数据成员 |
8+------------------+
阶段3:C构造函数执行完成后
xxxxxxxxxx
101C对象内存状态:
2+------------------+
3| vptr -> C_vtable | ← 指向C的虚函数表
4+------------------+
5| A的数据成员 |
6+------------------+
7| B的数据成员 |
8+------------------+
9| C的数据成员 |
10+------------------+
1. 安全性考虑
xxxxxxxxxx
261class Base {
2public:
3 Base() {
4 init(); // 在构造函数中调用虚函数
5 }
6 virtual void init() {
7 cout << "Base::init()" << endl;
8 }
9};
10
11class Derived : public Base {
12private:
13 int* data;
14public:
15 Derived() : data(new int[100]) {
16 // Base构造函数已经执行完毕
17 cout << "Derived constructor" << endl;
18 }
19
20 void init() override {
21 // 如果在Base构造函数中调用这个版本,data还未初始化!
22 for(int i = 0; i < 100; i++) {
23 data[i] = i; // data可能是nullptr
24 }
25 }
26};
如果在Base构造函数执行时就调用Derived的init(),会访问未初始化的成员,导致未定义行为。
2. 逐步构建对象的"身份"
xxxxxxxxxx
281void demonstrate_identity_change() {
2 class A {
3 public:
4 A() {
5 cout << "A构造时,typeid: " << typeid(*this).name() << endl;
6 // 输出类似:class A
7 }
8 virtual ~A() = default;
9 };
10
11 class B : public A {
12 public:
13 B() {
14 cout << "B构造时,typeid: " << typeid(*this).name() << endl;
15 // 输出类似:class B
16 }
17 };
18
19 class C : public B {
20 public:
21 C() {
22 cout << "C构造时,typeid: " << typeid(*this).name() << endl;
23 // 输出类似:class C
24 }
25 };
26
27 C obj; // 观察构造过程中对象"身份"的变化
28}
xxxxxxxxxx
551
2
3using namespace std;
4
5class A {
6public:
7 A() {
8 cout << "A构造函数中调用f(): ";
9 f(); // 调用A::f()
10 cout << "A构造函数中的类型: " << typeid(*this).name() << endl;
11 }
12
13 virtual ~A() = default;
14
15 virtual void f() {
16 cout << "A::f()" << endl;
17 }
18
19 void test_virtual_call() {
20 cout << "完整对象中调用f(): ";
21 f(); // 调用最终派生类的f()
22 }
23};
24
25class B : public A {
26public:
27 B() {
28 cout << "B构造函数中调用f(): ";
29 f(); // 仍然调用A::f(),因为B没有重写
30 cout << "B构造函数中的类型: " << typeid(*this).name() << endl;
31 }
32};
33
34class C : public B {
35public:
36 C() {
37 cout << "C构造函数中调用f(): ";
38 f(); // 调用C::f()
39 cout << "C构造函数中的类型: " << typeid(*this).name() << endl;
40 }
41
42 void f() override {
43 cout << "C::f()" << endl;
44 }
45};
46
47int main() {
48 cout << "=== 创建C对象 ===" << endl;
49 C obj;
50
51 cout << "\n=== 完整对象调用虚函数 ===" << endl;
52 obj.test_virtual_call();
53
54 return 0;
55}
输出结果:
xxxxxxxxxx
101=== 创建C对象 ===
2A构造函数中调用f(): A::f()
3A构造函数中的类型: class A
4B构造函数中调用f(): A::f()
5B构造函数中的类型: class B
6C构造函数中调用f(): C::f()
7C构造函数中的类型: class C
8
9=== 完整对象调用虚函数 ===
10完整对象中调用f(): C::f()
Important
vptr在构造过程中会动态更新:每个构造函数执行时,vptr都会指向当前类的虚函数表
构造函数中的虚函数调用是"安全的":只会调用当前已构造完成的类层次中的函数
对象的"类型身份"逐步建立:从基类逐步"进化"到最终的派生类类型
这种设计避免了未定义行为:防止调用使用未初始化成员的派生类函数
析构过程相反:vptr会从派生类逐步"退化"回基类
这种机制确保了C++对象在构造和析构过程中的类型安全性,是C++虚函数机制设计的精妙之处。
虚函数表会继承,每个包含虚函数的类都有自己的虚函数表,派生类的虚函数表是基于基类虚函数表构建的。
虚函数表继承的基本继承规则:
继承基类的虚函数表结构:派生类复制基类的虚函数表作为起点
重写函数替换:如果派生类重写了虚函数,用新函数地址替换表中对应位置
新增虚函数追加:派生类新增的虚函数追加到表的末尾
保持函数顺序:虚函数在表中的位置(偏移量)保持一致
基于之前的代码示例:
xxxxxxxxxx
211class A {
2public:
3 A() { cout << "A's cons." << endl; }
4 virtual ~A() { cout << "A's des." << endl; }
5 virtual void f() { cout << "A's f()." << endl; }
6 void g() { f(); }
7};
8
9class B : public A {
10public:
11 B() { f(); cout << "B's cons." << endl; }
12 ~B() { cout << "B's des." << endl; }
13 // 注意:B没有重写f(),也没有新的虚函数
14};
15
16class C : public B {
17public:
18 C() { cout << "C's cons." << endl; }
19 ~C() { cout << "C's des." << endl; }
20 void f() { cout << "C's f()." << endl; } // 重写了f()
21};
A类的虚函数表 (A_vtable)
xxxxxxxxxx
61A_vtable:
2+-------------------+
3| 偏移0: A::~A() | ← 虚析构函数
4+-------------------+
5| 偏移1: A::f() | ← 虚函数f()
6+-------------------+
说明:
A是基类,定义了虚函数表的基本结构
包含虚析构函数和虚函数f()
这个表结构将被所有派生类继承
B类的虚函数表 (B_vtable)
xxxxxxxxxx
61B_vtable:
2+-------------------+
3| 偏移0: B::~B() | ← 重写的析构函数
4+-------------------+
5| 偏移1: A::f() | ← 继承A的f()(B没有重写)
6+-------------------+
说明:
B继承了A的虚函数表结构
B重写了析构函数,所以偏移0位置指向B::~B()
B没有重写f(),所以偏移1位置仍然指向A::f()
B没有新增虚函数,表大小与A相同
C类的虚函数表 (C_vtable)
xxxxxxxxxx
61C_vtable:
2+-------------------+
3| 偏移0: C::~C() | ← 重写的析构函数
4+-------------------+
5| 偏移1: C::f() | ← 重写的f()函数
6+-------------------+
说明:
C继承了B的虚函数表结构(实际上是A的结构)
C重写了析构函数,所以偏移0位置指向C::~C()
C重写了f(),所以偏移1位置指向C::f()
C没有新增虚函数,表大小与A、B相同
步骤1:A类虚函数表创建
xxxxxxxxxx
51// 编译器为A类生成
2static void* A_vtable[] = {
3 (void*)&A::~A, // 偏移0
4 (void*)&A::f // 偏移1
5};
步骤2:B类虚函数表继承
xxxxxxxxxx
51// 编译器为B类生成(基于A的结构)
2static void* B_vtable[] = {
3 (void*)&B::~B, // 偏移0:重写析构函数
4 (void*)&A::f // 偏移1:继承A::f()(B没有重写)
5};
步骤3:C类虚函数表继承
xxxxxxxxxx
51// 编译器为C类生成(基于B的结构)
2static void* C_vtable[] = {
3 (void*)&C::~C, // 偏移0:重写析构函数
4 (void*)&C::f // 偏移1:重写f()函数
5};
xxxxxxxxxx
191class Base {
2public:
3 virtual ~Base() = default;
4 virtual void func1() { cout << "Base::func1" << endl; }
5 virtual void func2() { cout << "Base::func2" << endl; }
6};
7
8class Derived1 : public Base {
9public:
10 void func1() override { cout << "Derived1::func1" << endl; }
11 virtual void func3() { cout << "Derived1::func3" << endl; } // 新增
12};
13
14class Derived2 : public Derived1 {
15public:
16 void func2() override { cout << "Derived2::func2" << endl; }
17 void func3() override { cout << "Derived2::func3" << endl; }
18 virtual void func4() { cout << "Derived2::func4" << endl; } // 新增
19};
虚函数表结构对比
xxxxxxxxxx
321Base_vtable:
2+----------------------+
3| 偏移0: Base::~Base() |
4+----------------------+
5| 偏移1: Base::func1() |
6+----------------------+
7| 偏移2: Base::func2() |
8+----------------------+
9
10Derived1_vtable:
11+---------------------------+
12| 偏移0: ~Derived1() [自动生成] | ← 编译器自动生成的虚析构函数
13+---------------------------+
14| 偏移1: Derived1::func1() | ← 重写
15+---------------------------+
16| 偏移2: Base::func2() | ← 继承
17+---------------------------+
18| 偏移3: Derived1::func3() | ← 新增
19+---------------------------+
20
21Derived2_vtable:
22+---------------------------+
23| 偏移0: ~Derived2() [自动生成] | ← 编译器自动生成的虚析构函数
24+---------------------------+
25| 偏移1: Derived1::func1() | ← 继承Derived1的重写
26+---------------------------+
27| 偏移2: Derived2::func2() | ← 重写
28+---------------------------+
29| 偏移3: Derived2::func3() | ← 重写
30+---------------------------+
31| 偏移4: Derived2::func4() | ← 新增
32+---------------------------+
编译器在以下情况下自动生成析构函数:
类没有用户定义的析构函数
基类有虚析构函数
类有虚函数(某些情况下)
xxxxxxxxxx
231
2
3using namespace std;
4
5void print_vtable_info() {
6 A a;
7 B b;
8 C c;
9
10 // 获取vptr(第一个成员)
11 void** a_vptr = *(void***)&a;
12 void** b_vptr = *(void***)&b;
13 void** c_vptr = *(void***)&c;
14
15 cout << "A的虚函数表地址: " << a_vptr << endl;
16 cout << "B的虚函数表地址: " << b_vptr << endl;
17 cout << "C的虚函数表地址: " << c_vptr << endl;
18
19 // 验证虚函数表是否不同
20 cout << "A和B的虚函数表相同: " << (a_vptr == b_vptr) << endl;
21 cout << "B和C的虚函数表相同: " << (b_vptr == c_vptr) << endl;
22 cout << "A和C的虚函数表相同: " << (a_vptr == c_vptr) << endl;
23}
Important
虚函数表会继承:派生类基于基类的虚函数表结构构建自己的表
偏移量保持一致:相同的虚函数在所有类的虚函数表中都有相同的偏移量
重写替换,新增追加:
重写的函数替换表中对应位置
新增的虚函数追加到表末尾
每个类都有独立的虚函数表:即使内容相同,每个类也有自己的虚函数表实例
编译时确定结构:虚函数表的结构在编译时就确定了
运行时动态绑定:通过vptr和偏移量实现运行时的函数调用
在多层继承的C++对象析构过程中,析构函数的执行流程与构造函数正好相反,遵循派生类到基类的顺序。
在我们的例子中(A ← B ← C),当删除一个C类型的对象时,析构函数的调用顺序是:
xxxxxxxxxx
311. ~C() // 首先调用最派生类的析构函数
22. ~B() // 然后调用中间基类的析构函数
33. ~A() // 最后调用最基础类的析构函数
析构过程中,vptr会发生以下变化:
xxxxxxxxxx
61// 析构开始时:vptr指向C的vtable
2// 调用~C()时:vptr仍指向C的vtable
3// ~C()执行完毕:vptr更新为指向B的vtable
4// 调用~B()时:vptr指向B的vtable
5// ~B()执行完毕:vptr更新为指向A的vtable
6// 调用~A()时:vptr指向A的vtable
xxxxxxxxxx
451
2
3using namespace std;
4
5class A {
6public:
7 A() { cout << "A构造" << endl; }
8 virtual ~A() {
9 cout << "A析构,当前类型:" << typeid(*this).name() << endl;
10 f(); // 在析构函数中调用虚函数
11 }
12 virtual void f() { cout << "A::f()" << endl; }
13};
14
15class B : public A {
16public:
17 B() { cout << "B构造" << endl; }
18 virtual ~B() {
19 cout << "B析构,当前类型:" << typeid(*this).name() << endl;
20 f(); // 在析构函数中调用虚函数
21 }
22};
23
24class C : public B {
25public:
26 C() { cout << "C构造" << endl; }
27 virtual ~C() {
28 cout << "C析构,当前类型:" << typeid(*this).name() << endl;
29 f(); // 在析构函数中调用虚函数
30 }
31 virtual void f() override { cout << "C::f()" << endl; }
32};
33
34int main() {
35 cout << "=== 创建对象 ===" << endl;
36 A* ptr = new C();
37
38 cout << "\n=== 正常使用 ===" << endl;
39 ptr->f(); // 调用C::f()
40
41 cout << "\n=== 删除对象 ===" << endl;
42 delete ptr;
43
44 return 0;
45}
xxxxxxxxxx
151=== 创建对象 ===
2A构造
3B构造
4C构造
5
6=== 正常使用 ===
7C::f()
8
9=== 删除对象 ===
10C析构,当前类型:class C
11C::f()
12B析构,当前类型:class B
13A::f()
14A析构,当前类型:class A
15A::f()
Important
析构顺序:总是从最派生类开始,逐级向上到基类
vptr更新时机:每个析构函数执行完毕后,vptr才会更新到上一级基类的vtable
虚函数调用:在析构函数中调用虚函数时,会调用当前正在析构的类的版本,而不是最派生类的版本
安全性考虑:这种设计确保在析构过程中不会调用已经被析构的派生类成员
内存布局变化:
xxxxxxxxxx
41析构前:[vptr→C_vtable][A成员][B成员][C成员]
2~C()后:[vptr→B_vtable][A成员][B成员][已析构]
3~B()后:[vptr→A_vtable][A成员][已析构][已析构]
4~A()后:[已完全析构]
Important
虽然通过虚析构函数机制确实会首先调用C的析构函数,但析构过程并不会在C的析构函数执行完毕后就结束。
当执行 delete pa
时,完整的析构流程是:
通过虚析构函数机制找到C的析构函数:由于A的析构函数是虚函数,通过vptr找到实际对象类型C的析构函数
执行C的析构函数:输出 "C's des."
自动调用B的析构函数:C的析构函数执行完毕后,编译器自动调用基类B的析构函数,输出 "B's des."
自动调用A的析构函数:B的析构函数执行完毕后,编译器自动调用基类A的析构函数,输出 "A's des."
为什么会有析构链?
这是C++继承机制的基本规则:
构造时:从基类到派生类(A → B → C)
析构时:从派生类到基类(C → B → A)
每个类的析构函数只负责清理自己的资源,基类的资源必须由基类的析构函数来清理。
Tip
虚析构函数的作用:确保从基类指针删除派生类对象时,能正确调用派生类的析构函数
析构链的必要性:每个类都可能有自己的资源需要清理,所以必须调用整个继承链上的所有析构函数
自动调用机制:一旦开始析构过程,编译器会自动确保调用完整的析构链,这不是可选的
如果没有虚析构函数,delete pa
只会调用A的析构函数,导致B和C的资源无法正确清理,这就是为什么基类需要虚析构函数。
1. 初始状态
xxxxxxxxxx
21对象内存布局:[vptr→C_vtable][A部分][B部分][C部分]
2pa指向这个对象,vptr指向C的vtable
2. 开始析构(delete pa)
xxxxxxxxxx
201// 编译器生成的伪代码
2void delete_C_object(A* pa) {
3 // 1. 通过vptr找到C的析构函数并调用
4 pa->~C(); // 实际调用C::~C()
5
6 // 2. C的析构函数执行完毕后,vptr自动更新为B的vtable
7 // 对象变为:[vptr→B_vtable][A部分][B部分][已析构的C部分]
8
9 // 3. 自动调用B的析构函数
10 static_cast<B*>(pa)->~B(); // 直接调用,不通过虚函数
11
12 // 4. B的析构函数执行完毕后,vptr自动更新为A的vtable
13 // 对象变为:[vptr→A_vtable][A部分][已析构的B部分][已析构的C部分]
14
15 // 5. 自动调用A的析构函数
16 static_cast<A*>(pa)->~A(); // 直接调用,不通过虚函数
17
18 // 6. 释放内存
19 operator delete(pa);
20}
1. vptr的自动更新
C析构函数开始时:vptr指向C的vtable
C析构函数结束时:编译器自动将vptr更新为B的vtable
B析构函数结束时:编译器自动将vptr更新为A的vtable
2. 编译器如何找到基类析构函数
xxxxxxxxxx
121// 编译器在编译时就知道继承关系
2class C : public B { // 编译器知道C的基类是B
3public:
4 ~C() {
5 // 用户代码
6 cout << "C's des." << endl;
7
8 // 编译器自动添加的代码:
9 // 1. 更新vptr为B的vtable
10 // 2. 调用B::~B()
11 }
12};
3. 对象的"部分存在"状态
xxxxxxxxxx
41完整对象: [vptr→C][A部分][B部分][C部分]
2C析构后: [vptr→B][A部分][B部分][无效]
3B析构后: [vptr→A][A部分][无效][无效]
4A析构后: [无效][无效][无效][无效]
xxxxxxxxxx
271
2
3using namespace std;
4
5class A {
6public:
7 A() { cout << "A构造" << endl; }
8 virtual ~A() {
9 cout << "A析构,当前类型:" << typeid(*this).name() << endl;
10 }
11};
12
13class B : public A {
14public:
15 B() { cout << "B构造" << endl; }
16 virtual ~B() {
17 cout << "B析构,当前类型:" << typeid(*this).name() << endl;
18 }
19};
20
21class C : public B {
22public:
23 C() { cout << "C构造" << endl; }
24 virtual ~C() {
25 cout << "C析构,当前类型:" << typeid(*this).name() << endl;
26 }
27};
输出结果:
xxxxxxxxxx
61A构造
2B构造
3C构造
4C析构,当前类型:class C // vptr指向C的vtable
5B析构,当前类型:class B // vptr已更新为B的vtable
6A析构,当前类型:class A // vptr已更新为A的vtable
Important
编译器如何自动调用基类析构函数?
编译器在编译时就知道继承关系,会在每个析构函数的末尾自动插入调用基类析构函数的代码
vptr属于哪个类?
vptr始终属于当前"活着"的对象部分。C析构后,对象变成B类型,vptr指向B的vtable
C对象是否不存在了?
C的特有部分被析构了,但对象的A和B部分仍然存在
对象从C类型"退化"为B类型,然后再"退化"为A类型
后续调用不通过虚函数机制
只有第一次调用(C的析构)通过虚函数机制
后续的B和A析构函数调用是编译器直接插入的静态调用
这引出了一个新的问题,析构函数都在虚函数表里,但编译器在析构过程中却采用了特殊的调用机制而并没有使用虚函数表。
虽然析构函数在vtable中,但编译器在生成析构代码时采用了静态调用而不是虚函数调用:
xxxxxxxxxx
181// 编译器为C的析构函数生成的实际代码
2C::~C() {
3 // 用户代码
4 cout << "C's des." << endl;
5
6 // 编译器自动添加的代码(关键!)
7 this->vptr = &B_vtable; // 更新vptr
8 B::~B(); // 直接静态调用,不是this->~B()
9}
10
11B::~B() {
12 // 用户代码
13 cout << "B's des." << endl;
14
15 // 编译器自动添加的代码
16 this->vptr = &A_vtable; // 更新vptr
17 A::~A(); // 直接静态调用,不是this->~A()
18}
对比两种调用方式
xxxxxxxxxx
61// 虚函数调用(只在delete时使用一次)
2this->~C(); // 通过vptr查找vtable中的析构函数
3
4// 静态调用(后续基类析构使用)
5B::~B(); // 直接调用,不查vtable
6A::~A(); // 直接调用,不查vtable
为什么要这样设计?
原因1:避免无限递归
xxxxxxxxxx
81// 如果使用虚函数调用会发生什么?
2C::~C() {
3 cout << "C's des." << endl;
4 this->~B(); // 假设这样调用
5 // 但此时vptr仍指向C的vtable!
6 // this->~B()会再次调用C::~C()!
7 // 导致无限递归
8}
原因2:确保正确的析构顺序
xxxxxxxxxx
51// 静态调用确保按继承层次析构
2C::~C() -> B::~B() -> A::~A() // 正确顺序
3
4// 如果都用虚函数调用,可能出现问题
5// 因为vptr的更新时机很关键
原因3:性能考虑
xxxxxxxxxx
51// 静态调用更高效
2B::~B(); // 直接跳转,无需查表
3
4// 虚函数调用需要额外开销
5this->~B(); // 需要通过vptr查找vtable
完整的析构机制
xxxxxxxxxx
181// delete pa 的完整过程
2void delete_process(A* pa) {
3 // 第1步:通过虚函数机制找到最派生类的析构函数
4 pa->~A(); // 实际调用C::~C()(通过vtable)
5
6 // 在C::~C()内部:
7 // - 执行用户代码
8 // - vptr = &B_vtable
9 // - B::~B()(静态调用)
10
11 // 在B::~B()内部:
12 // - 执行用户代码
13 // - vptr = &A_vtable
14 // - A::~A()(静态调用)
15
16 // 第2步:释放内存
17 operator delete(pa);
18}
Note
只有第一次调用(从基类指针删除对象)使用虚函数机制
后续的基类析构调用都是编译器插入的静态调用
vptr的更新发生在每个析构函数的末尾
这种设计避免了递归调用,确保了正确的析构顺序
所以虽然析构函数在vtable中,但编译器巧妙地只在需要多态性的地方(第一次调用)使用虚函数机制,后续调用采用更安全、更高效的静态调用方式。