为什么不应该在构造和析构函数中调用虚函数

本文最后更新于:2022年11月22日 下午

其实这来自于 《Effective C++》 Rule 09:

Never call virtual functions during construction or destruction

这一点主要是因为构造函数和析构函数比较特殊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <bits/stdc++.h>
using namespace std;

class Base{
public:
Base(){
cout << "Call Base::constructor" << endl;
}

virtual ~Base(){
cout << "Call Base::destructor" << endl;
}
};

class Derived : public Base{
public:
Derived(){
cout << "Call Derived::constructor" << endl;
}
virtual ~Derived(){
cout << "Call Derived::destructor" << endl;
}
};

int main(void){
Base* d = new Derived;
delete d;
}

结果如下:

1
2
3
4
5
6
7
D:\Desktop\Study\course\cpp_wkspc\Leetcode\cmake-build-debug\Leetcode.exe
Call Base::constructor
Call Derived::constructor
Call Derived::destructor
Call Base::destructor

Process finished with exit code 0

子类Derived对象构造时:

  • 先调用基类Base构造函数
  • 再调用Derived构造函数

Derived对象析构时:

  • 先调用子类Derived析构函数
  • 再调用基类Base析构函数

所以如果在构造函数中调用虚函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Base{
public:
Base(){
TestConstruct();
cout << "Call Base::constructor" << endl;
}
virtual void TestConstruct(){
cout << "Call Base::TestConstruct" << endl;
}
virtual void TestDestruct(){
cout << "Call Base::TestDestruct" << endl;
};
virtual ~Base(){
TestDestruct();
cout << "Call Base::destructor" << endl;
}
};

class Derived : public Base{
public:
Derived(){
TestConstruct();
cout << "Call Derived::constructor" << endl;
}
virtual void TestConstruct(){
cout << "Call Derived::TestConstruct" << endl;
}
virtual void TestDestruct(){
cout << "Call Derived::TestDestruct" << endl;
};
virtual ~Derived(){
TestDestruct();
cout << "Call Derived::destructor" << endl;
}
};

int main(void){
Base* d1 = new Base();
delete d1;
cout << "-----------------------------------"<<endl;
Base* d2 = new Derived();
delete d2;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
D:\Desktop\Study\course\cpp_wkspc\Leetcode\cmake-build-debug\Leetcode.exe
Call Base::TestConstruct
Call Base::constructor
Call Base::TestDestruct
Call Base::destructor
-----------------------------------
Call Base::TestConstruct
Call Base::constructor
Call Derived::TestConstruct
Call Derived::constructor
Call Derived::TestDestruct
Call Derived::destructor
Call Base::TestDestruct
Call Base::destructor

Process finished with exit code 0

所以如果我们想要在构造函数和析构函数中为子类和父类调用不同版本的虚函数,可能会失望:因为子类实际上会一共执行两个版本的虚函数

这样的行为背后的逻辑是Derived对象的Base class 构造期间,对象的类型是Base而不是Derived,所以 virtual function 会被编译器解析至Base.这是非常合理的,因为Derived中的虚函数绝大多数都会使用到属于Derived部分的成员变量,而那些成员变量在此刻尚未初始化,使用未初始化的成员对象存在风险。

而对于析构函数,进入Base析构函数后,对象的类型也被视作Base而非Derived.由于Derived的析构函数会先于Base析构函数执行,当进入Base析构函数时,Derived部分的成员变量应当已经被析构而呈现未定义值,所以也不应该使用它们。


为什么不应该在构造和析构函数中调用虚函数
https://flaglord.com/2021/09/14/为什么不应该在构造和析构函数中调用虚函数/
作者
flaglord
发布于
2021年9月14日
许可协议