基类中的虚析构函数
转自:http://glatue.com/category/basic-knowledge/
场景
如果一个类会被作为基类,那么基类的析构函数最好声明为虚函数。
原因是为了避免下面这样的操作,造成派生类的析构函数不能被调用。
Base *d = new Drived(); delete d; //如果Base类中的析构函数是非虚的,那么此delete操作只会调用Base的析构函数,而不会调用Drived的析构函数。
把Base的析构函数定义成虚函数,则能够在上面的delete操作中调用到Drived的析构函数
底层原理
首先明白以下几点:
1. 如果Base的析构函数声明为虚函数,则Drived的析构也会是虚函数(即使不定义为virtual),并且编译器会把Drived的析构函数当做是Base的析构函数的一个实现。
2. 如果Drived的析构函数被调用,则Base的析构函数会在之后也会被调用到。
那么我们来看看当Base的析构函数被定义为虚函数以后,会发生什么?
1. 当 new Drived() 时
Base被创建,并且创建自身的虚函数表,表中指针指向Base的析构函数
Drived基于Base被创建,并在Base的虚函数表上进一步的修改,将表中指向Base的析构函数指针指向自身的析构函数位置。
那么此时,Drived中的虚函数表中的析构函数指针,指向自身的析构函数。
2. 当 Base *d = new Drived() 时
编译器会把new Drived() 产生的对应的内存位置当做Base来处理(此时Drived虚函数表中的指针并没有改变)
3. 当 delete d 时
此时会去调用析构函数。
此时d的类型是Base,而Base的析构函数是虚函数,所以需要去虚函数表里找,而此时,虚函数表里析构函数的指针指向的是Drived的析构函数,所以调用析构函数时实际上调用了Drived的析构函数。
而Base是Drived的基类,所以调用完Drived的析构函数后,会自动调用Base的析构函数。
4. 那么这样一来
无论delete谁,都会调用到最“子”的对象的析构函数,进而调用到所有父类的析构函数。就避免了子类析构不被调用的问题。
验证的代码
class Base { public: Base(){cout << "Base" << endl;}; virtual ~Base(){cout << "Base -" << endl;}; }; class Drived : public Base { public: Drived(){cout << "Drived" << endl;}; ~Drived(){cout << "Drived -" << endl;}; }; int main() { Base *d = new Drived(); delete d; return 0; }
输出
Base Drived Drived - Base -