虚拟析构函数和内存释放

时间:2013-08-04 20:06:38

标签: c++ heap virtual-destructor

我不太确定我理解虚拟析构函数以及在堆上分配空间的概念。我们来看下面的例子:

class Base
{
public:
    int a;
};

class Derived : public Base
{
public:
    int b;
};

我想如果我做这样的事情

Base *o = new Derived;

在堆上分配8个字节(或系统上需要的两个整数),看起来像这样: ...... | a | b | ...

现在,如果我这样做:

delete o;

如何'删除'知道,为了从堆中删除所有内容,o实际上是哪种类型?我想它必须假设它是Base类型,因此只从堆中删除a(因为它不能确定b是否属于对象o): ...... | b | ...

然后

b将保留在堆上并且无法访问。

以下内容:

Base *o = new Derived;
delete o;

真正引发内存泄漏,我需要一个虚拟析构函数吗?或者删除是否知道o实际上是Derived类,而不是Base类?如果是这样,那该怎么办?

谢谢你们。 :)

3 个答案:

答案 0 :(得分:3)

您可能会对实施做出很多假设 或者可能不会举行。在delete表达式中,动态类型必须是 与静态类型相同,除非静态类型具有虚拟类型 析构函数。否则,它是未定义的行为。期。那是 真的你必须知道 - 我已经在实施中使用了 否则会崩溃,至少在某些情况下;而且我已经习惯了 这样做的实现会破坏自由空间竞技场,所以 代码会在一段时间后崩溃,完全不相关的部分 代码(为了记录,VC ++和g ++都属于第二种情况,at 至少在使用通常的已发布代码选项进行编译时。)

答案 1 :(得分:2)

首先,您在示例中声明的类具有简单的内部结构。从纯粹实际的角度来看,为了正确地销毁这些类的对象,运行时代码不需要知道被删除对象的实际类型。它需要知道的是要解除分配的内存块的正确大小。这实际上是由mallocfree等C风格的库函数实现的。您可能知道,free隐式“知道”要释放多少内存。上面的示例除此之外不涉及任何其他内容。换句话说,上面的例子不够精细,无法真正说明任何特定于C ++的东西。

但是,正式地,您的示例的行为是未定义的,因为C ++语言正式要求虚拟析构函数用于多态删除,而不管类的内部结构是多么微不足道。所以,你的“如何delete知道...”问题根本不适用。你的代码坏了。它不起作用。

其次,当您开始要求对类进行非平凡的破坏时,实际的有形C ++特效会开始出现:要么通过为析构函数定义显式主体,要么通过向类中添加非平凡的成员子对象。例如,如果向派生类添加std::vector成员,派生类的析构函数将负责(隐式)销毁该子对象。为了使其起作用,您必须声明析构函数virtual。通过与调用任何其他虚函数相同的机制调用正确的虚析构函数。这基本上就是你的问题的答案:运行时代码并不关心对象的实际类型,因为普通的虚拟调度机制将确保调用正确的析构函数(就像它适用于任何其他虚函数一样)。

第三,当您为类定义专用operator delete函数时,会出现虚拟破坏的另一个重要影响。语言规范要求选择正确的operator delete函数,就好像从被删除类的析构函数中查找它一样。许多实现从字面上实现了这个要求:它们实际上是从类析构函数中隐式调用operator delete。为了使该机制正常工作,析构函数必须是虚拟的。

第四,你问题的一部分似乎暗示你认为未能定义虚拟析构函数会导致“内存泄漏”。这是一个流行的,但完全不正确且完全没用的都市传奇,由低质量的资源延续。在没有虚拟析构函数的类上执行多态删除会导致未定义的行为并导致完全不可预测的破坏性后果,而不是某些“内存泄漏”。在这种情况下,“内存泄漏”不是问题。

答案 2 :(得分:0)

被删除对象的大小没有问题 - 众所周知。虚拟析构函数解决的问题可以证明如下:

class Base
{
public:
    Base() { x = new char[1]; }
    /*virtual*/ ~Base() { delete [] x; }

private:
    char* x;
};

class Derived : public Base
{
public:
    Derived() { y = new char[1]; }
    ~Derived() { delete [] y;}
private:
    char* y;
};

然后:

Derived* d = new Derived();
Base* b = new Derived();

delete d;   // OK
delete b;   // will only call Base::~Base, and not Derived::~Derived

第二次删除不会正确完成对象。如果取消注释“virtual”关键字,那么第二个delete语句将按预期运行,它将调用Derived :: ~Derived和Base :: ~Base。

编辑:正如评论中指出的那样,严格来说,第二次删除会产生一种未定义的行为。