C++析构函数:内存何时被释放?

6
如果我删除一个对象,导致其析构函数被调用,内存是在析构函数完成执行之前还是之后被释放的?
6个回答

8

只有最不派生的类子对象被销毁后,内存才会被释放。因此,如果您有:

class Base {
};

class Derived : public Base {
public:
    ~Derived();
};

首先销毁Derived,然后销毁Base,最后才释放内存。


有这样的东西吗?调用特定的析构函数,如果类声明为虚拟,则可能是虚拟的。该析构函数负责销毁所有子对象,然后释放内存。所有没有基类的子对象都是同样最不派生的,没有特定的责任或意义。 - Potatoswatter
@Potatoswatter:请注意,我没有具体说明谁负责调用释放函数——正因为在所有细节上这样做需要一个两页的答案。 - sharptooth
@Potatoswatter:我不确定我是否理解了。没有析构函数负责销毁子对象。析构函数负责释放该对象持有的资源,但不必(也不应该)调用其他对象的析构函数:class test { string a; ~test() {} }; 在这个例子中,~test 析构函数是完全定义好的,类中没有直接管理的资源,因此什么也不做。系统将在 ~test 执行完成后,在 a 子对象上调用 ~string,而不是 ~test。继承也是一样的,基类的析构函数会自动调用。 - David Rodríguez - dribeas
请注意,需要处理的是调用最派生类的析构函数。在实现这一点之后,系统将确保以构造顺序相反的顺序调用所有base的析构函数。回到责任问题,如果成员持有需要特定管理且不由该类型的析构函数持有的资源--比如需要delete的指针并且没有析构函数--那么该资源必须在包含类中进行管理。 - David Rodríguez - dribeas
@David:“没有析构函数负责销毁子对象。” 好吧,不是显式地。你不必在~Derived()里面显式调用this->~Base()。然而,至少从正式定义的方式来看,~Derived()应该负责调用this->~Base()(以及你评论示例中的a.~string())。当使用delete时,一个析构函数被调用,并且该析构函数负责调用所有清理工作,包括调用任何基类或成员析构函数(如果需要的话)。 - James McNellis
@sharptooth:问题在于没有单一的最不派生子对象。更好的说法是“在所有子对象被销毁之后,也就是析构函数体执行完毕之后”。 - Potatoswatter

4

delete分解为实际执行的步骤,就能比较清楚地看到何时删除了内存。因此,这样一个语句:

delete some_ptr;

大致相当于这个伪代码:

some_ptr->~some_ptr();
free( some_ptr );

因此,内存在调用析构函数后被释放。析构函数的确切操作并非由delete操作符确定,而是由类的定义确定。通常,它会进行本地清理并确保也调用其基类析构函数。
重要的是要意识到,释放内存实际上并不是析构函数的一部分。实际上是delete操作符释放了内存。
请注意,伪代码中的free函数实际上是已删除类或全局类别的operator delete()函数之一。这实际上释放了内存。

那不是 free(),那是 operator delete() - sharptooth
@sharptooth,你能澄清一下吗?我确实提到了那是“伪代码”。 - edA-qa mort-ora-y
1
即使是伪代码,C++也使用operator delete() - 全局或类特定的 - 用于在delete语句上释放内存。 - sharptooth
是的,没错。不过对于伪代码来说,我认为“free”更容易理解(而不是在其中进行递归删除)。 - edA-qa mort-ora-y
@sharptooth:用自身的术语来描述运算符是很困难的。使用free()作为示例是可以接受的,只要你提到它是伪代码即可。不过我会使用release()来避免与任何特定API混淆。 - Martin York
@Martin York:嗯,是的,人们往往会混淆delete语句和operator delete()函数。我想你是对的——像deallocateMemory()这样的伪代码会更好。 - sharptooth

2
析构函数完成后,内存将被释放。否则,在析构函数中访问成员变量会导致段错误。

2

析构函数执行完后,会调用operator delete释放内存,但具体什么时候释放内存取决于所使用的分配器。


0

我认为内存是在析构函数执行完毕后释放的。我知道当捕获异常时,对象的析构函数直到对象本身超出作用域才会被调用。


0

在C++中,析构是使用对象中可用的数据执行某些代码。这段代码是任意的。

释放内存是一种低级处理,通常由delete运算符隐藏,不应在调用析构函数之前调用。

这最好由分配器接口总结:

  • allocatedeallocate用于操作原始内存
  • constructdestroy用于调用对象的构造函数和析构函数

它明确指出,constructdestroydeallocate只能在先前由该分配器分配的内存上执行。它还明确指出,destroy不会释放内存,需要随后调用deallocate

请注意,这是一个低级接口,允许销毁一个对象并重用释放的空间来就地构造另一个对象。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接