c ++中的虚拟析构函数

时间:2014-10-24 12:15:51

标签: c++ virtual virtual-destructor

在下面的代码中,为什么~Derived()析构函数会自动调用?

#include<iostream>
using namespace std;
class Base
{
public:
    virtual ~Base()
    {
        cout << "Calling ~Base()" << endl;
    }
};

class Derived: public Base
{
private:
    int* m_pnArray;

public:
    Derived(int nLength)
    {
        m_pnArray = new int[nLength];
    }

    virtual ~Derived()
    {
        cout << "Calling ~Derived()" << endl;
        delete[] m_pnArray;
    }
};

int main()
{
    Derived *pDerived = new Derived(5);
    Base *pBase = pDerived;
    delete pBase;

    return 0;
}

4 个答案:

答案 0 :(得分:3)

因为你的基类析构函数是虚拟的

virtual ~Base();

对指向基类的指针的delete调用导致对析构函数的虚拟调用,并且任何虚拟调用都将调度到派生类中的匹配函数。它不仅好,而且必要:否则行为是不确定的。

这对于析构函数不是空函数的派生类至关重要。否则,非虚拟调用会导致调用基类析构函数,派生资源泄露等等。

答案 1 :(得分:1)

当一个类中至少有一个virtual函数时,编译器会为列出成员函数指针的类创建一个表。考虑:

struct Base
{
    virtual ~Base() { };

    int n_;
};

伪代码中,您可以想象编译器添加:

void* Base::__virtual_dispatch_table[] = { (void*)&Base::~Base };

然后,当你有一个Base类型的实际对象时,它会有一个额外的隐藏数据成员,指向Base::__virtual_dispatch_table(“VDT”):

Variable definition       Memory layout
-------------------       -------------
Base myBase;              int n_;
                          void** __p_vdt = Base::__virtual_dispatch_table;

现在,如果您有Base* pdelete p;,编译器会说“嘿 - 它是virtual - 我不会硬打电话给Base::~Base,而是我将生成执行类似伪代码的代码:

void (Base::*p_destructor) = p->__p_vdt[0]
*p_destructor(p);   // "p" will provide the "this" value while the destructor runs

你为什么要这么做?因为当你带来一个Derived对象......

class Derived: public Base
{
private:
    int* m_pnArray;
    ...

...编译器可以创建一个单独的虚拟调度表......

void* Derived::__virtual_dispatch_table[] = { (void*)&Derived::~Derived };

... andd布局Derived对象的内存如下:

Variable definition       Memory layout
-------------------       -------------
Derived derived;          int n_;
                          void** __p_vdt = Derived::__virtual_dispatch_table;
                          int* m_pnArray;

请注意,__p_vdt在对象布局中位于相同的相对位置,但现在指向Derived类的虚拟调度表?

现在,如果你创建一个Base*derived,那么调用Base对象的析构函数所需的完全相同的代码,如果你丢失了轨道,那么...

void (Base::*p_destructor) = p->__p_vdt[0]
*p_destructor(p);   // "p" will provide the "this" value while the destructor runs

...可以运行,但最终会使用Derived对象的__p_vdt Derived::__virtual_dispatch_table值,并找到Derived类的析构函数。

答案 2 :(得分:0)

因为它允许您将任何Base对象(实际上可能是Derived)视为您可以删除的对象。

在这种情况下,如果delete pBase没有调用Derived析构函数,m_pnArray保存的数据将永远不会被删除,即会发生“内存泄漏”。

答案 3 :(得分:0)

致电时

delete pBase;

它查看pBase的虚函数表,找到适当的析构函数来开始展开,然后找到Derived :: ~Endived,然后沿着栈的方式工作。