构造函数析构函数中能不能使用虚函数

面试时有一个经典问题是 “在构造函数和析构函数中能否调用虚函数”,准备面试时,我还没有系统学习 C++,故而硬背的答案:不能。记得腾讯技术终面的时候面试官也问了我这个问题,我较为熟练地说出了自己的答案,如在构造函数中能否使用的回答大致为:

不能。我想从两个方面来回答这个问题。首先一个对象的虚函数表是在其构造过程中生成,构造过程中还没有完全生成好,在这个角度看在构造函数中调用虚函数是不合理的。第二,构造过程中,编译器按照从基类到派生类的顺序依次构造,在一些阶段处于未完成状态,这时编译器会将我们的对象看做是其当前状态的类,而非目的类的当前类阶段,故不可以调用虚函数。

现在几天在看 《C++ Primer》,打算系统地学习下 C++。在 P556 页中,刚好讲到了这个问题,读起来总有些不懂的地方,于是特意上网搜索了下,搜到了份我感觉不错的代码,在这解析下。

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
#include<iostream>
class A
{
public:
A() : val(0) {test();}
virtual void func()
{
std::cout << val << std::endl;
}
void test()
{
func();
}
int val;
};

class B : public A
{
public:
B() {test();}
void func()
{
++val;
std::cout << val << std::endl;
}
};

int main()
{
A* p = new B;
p->test();
return 0;
}

自己运行了下,其结果为

1
2
3
0
1
2

解析下程序,我们首先看到在 AB 两类的构造函数中都调用了非虚函数 test(), 而 test() 函数功能则是调用虚函数 func(), 在 AB 两类中都定义了自己的 func 函数, Afunc 直接输出 val, 而 Bfunc 则先自增 val , 而后输出。

main 函数中,A* p = new B 构造了一个类 B 的对象,而将其赋给基类 A 的指针。在构造 B 的对象时,我们依次调用了 A 的构造函数和 B 的构造函数。A 的构造函数向 val 赋值为 0,并输出它,这是结果中输出 0 的缘由。在 B 的构造函数中,我们调用 this->test()test() 函数又调用了 this->func(),故调用了类 B 版本的 func() 函数,这是结果中 1 的来源。

main 函数中 p->test() 语句,由于 test() 为非虚函数,故静态绑定到了类 Atest() 函数,test() 函数调用 this->func()p->func(),为动态绑定,故绑定到类 Bfunc() 函数,故输出 2。

如此看来,在构造函数、析构函数中调用虚函数是有规律可循的,但较易出错。假若我们要使用时,一定要理解其中机制,合理谨慎设计。如果让我再回答这个问题,我较倾向回答为谨慎使用、尽量不使用。