`delete this;`语句发生了什么?

11
请参考以下代码:
class foo
{
public:
    foo(){}
    ~foo(){}
    void done() { delete this;}
private:
    int x;
};

以下两个选项中发生了什么(并且是否有效):

选项1:

void main()
{
   foo* a = new foo();
   a->done();
   delete a;
}

选项2:

void main()
{
   foo a;
   a.done();
}

在选项1中,第二个delete a;语句是否会导致异常或堆破坏?

在选项2中,是否会导致异常或堆破坏?


你是不是因为疏忽忘记了类的开头括号,还是这段代码就是复制粘贴的? - Neophile
有趣。我猜第一个会导致段错误或堆损坏,而第二个会对栈中指针进行删除操作。 - cha0site
2
void main() 不是有效的 C++。 - Kerrek SB
参见:http://www.parashift.com/c++-faq-lite/freestore-mgmt.html#faq-16.15。你的第一个例子违反了#4,你的第二个例子违反了#1。 - Fred Larson
7个回答

20

delete this;是允许的,它会删除对象。

你的这两个代码片段都有未定义的行为 - 在第一个情况下删除已经被删除的对象,在第二个情况下删除具有自动存储期限的对象。

由于行为未定义,标准没有说明它们是否会导致异常或堆损坏。对于不同的实现,可能是其中之一、两者都不是,也可能每次运行代码时都不同。


希望如此,它将是前者。 - Xeo

4

两者都会导致错误。

第一个指针被删除了两次,第二个delete导致错误,而第二个指针是在栈上分配的,无法隐式删除(由于第一次调用析构函数而导致错误)。


我编辑了这个问题。我认为它应该在堆上分配,然后用 done 删除。在这种情况下会发生什么?在堆栈上删除没有意义,而且可能不是 OP 所要求的。 - duedl0r
你的回答是正确的,但问题是会出现什么类型的错误。 - cha0site
@duedl0r 这是做出的假设。我认为OP就是这个意思。 - Linus Kleen
目前 OP 的选项 2 是 foo a; a.done(); 这将在非自由存储指针上调用 delete,因此不正确。 - bames53
@bames53:当然可以,但选项1会导致双重释放,并且也不正确。因此我认为这个问题是关于C++的故障模式。 - cha0site
显示剩余3条评论

3

这个问题已经被回答了,但我想补充一点:如果你的类确实调用了delete this,那么你也应该将析构函数设为私有。

这样可以确保只有这个类自己才能删除自己。

如果你将析构函数设为私有,上面提供的两个代码样例都会编译失败。


当然,这并不能防止所有错误,例如在调用done()后继续使用对象(即使再次调用done()也会导致双重删除)。 - CashCow

1

调用delete this是一个不好的想法。谁调用了new就应该调用delete。因此,其他回答中所强调的问题也会出现。

当构造对象数组时,还可能出现内存泄漏/未定义行为。


@JamesKanze 为什么?delete this 不是释放对象的正常方式。 - BЈовић
@VJovic 这取决于应用程序。在许多应用程序中,最频繁(甚至是唯一)的“delete”将是“delete this”。在其他情况下,“delete”将系统地出现在事务管理器或类似的位置。而在其他情况下...这取决于您使用动态内存的原因。如果是用于模型对象,则“delete this”或事务管理器是最常见的。如果是用于未知大小的复杂数据结构,则结构的所有者将执行“delete”,而“delete this”几乎不会发生。 - James Kanze
2
@JamesKanze:我希望你是在开玩笑!几乎没有任何情况下delete this是合适的,因为一个对象不应该知道或关心它自己的存储类别。(或者我非常好奇看到一个合法的用例。)这将是一种可怕的责任混淆。 - Kerrek SB
在一个大型系统中,对象可能具有复杂且可能是异步的销毁/终结过程。在这种情况下,通常最好告诉对象你已经使用完毕,并让对象在终结后自我销毁。不需要额外时间的对象可以同步自我销毁,但它们是否这样做是实现细节,不应该在接口中暴露。COM 的 Add_Ref()/Release() 是这种模式的一个典型例子。 - Simon Richter
@EtiennedeMartel 这取决于应用程序领域,但我从未见过智能指针可以正确处理更多的对象。如果您开始使用shared_ptr作为贫穷人的垃圾回收,那么您就需要在各处添加dispose函数,就像在Java中一样。 - James Kanze
显示剩余5条评论

1

两者都会导致错误,你想要的是:

void main()
{
   foo* a = new foo();
   a->done();
}

编译器将展开为类似以下内容,希望这使得删除“this”变得不那么混乱。

void __foo_done(foo* this)
{
   delete this;
}

void main()
{
   foo* a = new foo();
   __foo_done(a);
}

请参阅,成员函数在说delete this是合法(且道德)的吗?

如果它是标准布局(或POD)类中对象的第一个成员,该对象是使用普通new分配的,并且没有其他析构函数怎么办?(不是说这不会疯狂,但是...) - Random832
@Random832:抱歉,我无法理解你的问题。 - ronag
你提供的FAQ问题链接列出了一系列delete this是安全的条件。由于标准布局类的第一个成员具有与父对象本身相同的地址,因此理论上似乎又是另一种情况。 - Random832
@Random832:它的工作方式与非“delete this”情况相同。 - ronag

0

在释放内存的上下文中,我会非常谨慎。虽然调用 "delete this;" 将尝试释放与类结构相关联的内存,但该操作无法推断原始内存分配的上下文,即它是从堆还是栈分配的。我的建议是重新设计代码,以便从外部上下文调用解除分配操作。请注意,这与释放类内部结构不同,在这种情况下,请使用析构函数。


0
在这两种情况下,你的堆都会被破坏。当然,如果左值(在本例中为"a")不是指针,则不能在选项2中使用"->",选项2的正确形式应为a.done()。但是,如果你试图删除已经删除的内容或局部变量,你应该会遇到堆破坏。
调用 "delete this" 在技术上是有效的并释放内存。 编辑 我很好奇,实际尝试了一下:
class foo 
{
public: 
    foo(){} 
    ~foo(){} 
    void done() { delete this; } 
    void test() { x = 2; }
    int getx() { return x; }
private: 
    int x; 
} ;


int main(int argc, char* argv[])
{
    foo *a = new foo();
    a->done();
    a->test();
    int x = a->getx();
    printf("%i\n", x);
    return x;
}

调用printf将成功,并且x将保持值2


谢谢 - 实际上,option2中的“->”用法是由外部编辑器造成的打字错误 - 我已经再次更新了问题。 - NirMH
那么,delete this会释放堆中的对象内存吗? - NirMH
@NirMH: "->" 的使用是你写的。 - duedl0r
@NirMH,只要你使用 new 来分配内存就可以。 - Dejan Janjušević

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