在下面的代码中,为什么~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;
}
答案 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* p
和delete 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,然后沿着栈的方式工作。