【校招面试 之 C/C++】第10题 C++不在构造函数和析构函数中调用虚函数

时间:2021-07-30 06:46:22

1、不要在构造函数中调用虚函数的原因

在概念上,构造函数的工作是为对象进行初始化。在构造函数完成之前,被构造的对象被认为“未完全生成”。当创建某个派生类的对象时,如果在它的基类的构造函数中调用虚函数,那么此时派生类的构造函数并未执行,所调用的函数(派生类的虚函数)可能操作还没有被初始化的成员,将导致灾难的发生。

2、不要在析构函数中调用虚函数的原因

同样的,在析构函数中调用虚函数,函数的入口地址也是在编译时静态决定的。也就是说,实现的是实调用而非虚调用。 
考察如下例子。

#include <iostream>
using namespace std; class A{
public:
virtual void show(){
cout<<"in A"<<endl;
}
virtual ~A(){show();}
}; class B:public A{
public:
void show(){
cout<<"in B"<<endl;
}
}; int main(){
A a;
B b;
}

程序输出结果是: 
in A 
in A

在类B的对象b退出作用域时,会先调用类B的析构函数,然后调用类A的析构函数,在析构函数~A()中,调用了虚函数show()。从输出结果来看,类A的析构函数对show()调用并没有发生虚调用。

从概念上说,析构函数是用来销毁一个对象的,在销毁一个对象时,先调用该对象所属类的析构函数,然后再调用其基类的析构函数,所以,在调用基类的析构函数时,派生类对象的“善后”工作已经完成了,这个时候再调用在派生类中定义的函数版本已经没有意义了。

因此,一般情况下,应该避免在构造函数和析构函数中调用虚函数,如果一定要这样做,必须清楚,这是对虚函数的调用其实是实调用

===============================补充=======================

Effective C++条款9讲构造函数中不要调用虚函数已经很清楚了,析构函数与其类似

我猜你的疑惑在于不了解基类与派生类析构函数的执行顺序,C++中派生类在构造时会先调用基类的构造函数再调用派生类的构造函数,析构时则相反,先调用派生类的析构函数再调用基类的构造函数。

假设一个派生类的对象进行析构,首先调用了派生类的析构,然后在调用基类的析构时,遇到了一个虚函数,这个时候有两种选择:Plan A是编译器调用这个虚函数的基类版本,那么虚函数则失去了运行时调用正确版本的意义;Plan B是编译器调用这个虚函数的派生类版本,但是此时对象的派生类部分已经完成析构,“数据成员就被视为未定义的值”,这个函数调用会导致未知行为。

实际情况中编译器使用的是Plan A,如果虚函数的基类版本不是纯虚实现,不会有严重错误发生,但你依然会困惑虚函数机制失效,说不准又是“一张通往彻夜调试的直达车票”,所以Effective C++建议不要这么干

(主要要清楚在继承中构造函数初始化的顺序(先基类后派生类)、析构函数调用的顺序(先派生类后基类))