基类中的虚析构函数

转自: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 -
posted @ 2017-03-12 16:13  demianzhang  阅读(897)  评论(0编辑  收藏  举报