可以删除吗?


232

是否允许delete this;delete-statement是将在该类实例上执行的最后一条语句?当然,我确定this-pointer 表示的对象是正确new创建的。

我正在考虑这样的事情:

void SomeModule::doStuff()
{
    // in the controller, "this" object of SomeModule is the "current module"
    // now, if I want to switch over to a new Module, eg:

    controller->setWorkingModule(new OtherModule());

    // since the new "OtherModule" object will take the lead, 
    // I want to get rid of this "SomeModule" object:

    delete this;
}

我可以这样做吗?


13
主要的问题是,如果您delete this在类和用于创建该类对象的分配方法之间创建了紧密的耦合。这是非常糟糕的OO设计,因为OOP中最基本的事情是创建不知道或不在乎调用者正在做什么的自治类。因此,正确设计的类不应了解或关心它的分配方式。如果您出于某种原因需要这种特殊的机制,我认为更好的设计是在实际类周围使用包装器类,然后让包装器处理分配。
隆丁

您不能删除setWorkingModule吗?
Jimmy T.

Answers:


238

C ++ FAQ Lite有一个专门为此目的的条目

我认为这句话很好地总结了一下

只要您小心,对象可以自杀(删除此选项)。


15
相应的FQA也有一些有用的注释:yosefk.com/c++fqa/heap.html#fqa-16.15
Alexandre C. 2010年

1
为了安全起见,您可以在原始对象上使用私有析构函数,以确保其不在堆栈上或作为数组或向量的一部分构建。
Cem Kalyoncu 2015年

定义“小心”
CinCout

3
链接的常见问题解答文章中定义了“谨慎”。(尽管FQA链接大多令人讨厌-像其中的几乎所有内容一样-C ++多么糟糕)
CharonX

88

是的,delete this;已经定义了结果,只要(如您所指出的那样)您可以确保对象是动态分配的,并且(当然)在对象被销毁后就永远不要尝试使用它。多年来delete this;,相对于删除其他指针,已经问了很多有关标准专门说明的问题。答案很简短,很简单:它什么也没说。它只是说delete操作数必须是一个表达式,该表达式指定一个指向对象或对象数组的指针。它对如何计算释放内存的调用(如果有的话)释放函数的细节进行了详细介绍,但是delete(§[expr.delete])的整个部分都没有delete this;特别提及。关于destrucors的部分确实提到了delete this 放在一个地方(第[class.dtor] / 13节):

在定义虚拟析构函数时(包括隐式定义(15.8)),将确定非数组释放函数,就好像对于表达式delete出现在析构函数类的非虚拟析构函数中一样(请参见8.3.5)。 )。

这倾向于支持该标准认为delete this;有效的想法-如果该标准无效,则其类型将无意义。delete this;据我所知,这是标准所提到的唯一地方。

无论如何,有些人认为这delete this是令人讨厌的骇客,并告诉愿意听的人应该避免这种情况。一个常被提及的问题是难以确保仅动态分配该类的对象。其他人则认为它是完全合理的习惯用法,并且一直在使用。就我个人而言,我处于中间位置:我很少使用它,但是当它似乎是完成工作的正确工具时,请毫不犹豫地这样做。

初次使用此技术时,所使用的对象的寿命几乎完全是它自己的。James Kanze引用的一个例子是他为电话公司工作的计费/跟踪系统。开始打个电话时,会注意到这一点并创建一个phone_call对象。从那时起,该phone_call对象将处理电话的详细信息(在您拨打电话时建立连接,将一个条目添加到数据库中以说明通话开始的时间,如果您进行电话会议,则可能会连接更多的人,等等)。最后一次通话挂断时,phone_call对象将进行最后的簿记(例如,将一个条目添加到数据库中以告知您何时挂断,以便他们可以计算出通话的时间),然后销毁自身。的寿命phone_call对象基于第一个人何时开始呼叫以及最后一个人何时离开呼叫-从系统其余部分的角度来看,它基本上是任意的,因此您不能将其绑定到代码中的任何词法范围,或该订单上的任何内容。

对于任何可能在乎这种编码的可靠性的人:如果您拨打电话到,发往欧洲或几乎遍及欧洲的任何地方,那么很有可能代码会(至少部分地)对其进行处理。正是这样做的。


2
谢谢,我把它放在我的记忆中。我想您将构造函数和析构函数定义为私有,并使用一些静态工厂方法创建此类对象。
Alexandre C.

@Alexandre:在大多数情况下,您可能都会这样做-我不知道他所使用的系统的所有细节,因此我不能肯定地说。
杰里·科芬

我经常回避如何分配内存的问题的方法是bool selfDelete在构造函数中包含一个分配给成员变量的参数。诚然,这意味着要给程序员足够的绳索以在其中系上绞索,但我发现这比内存泄漏更可取。
MBraedley16年

1
@MBraedley:我也做过同样的事情,但是更喜欢避免在我看来像个混蛋。
杰里·科芬

对于任何可能关心...的人来说,很有可能被完全正确的代码(至少部分地)对其进行处理this。是的,代码正由完全处理this。;)
星系

46

如果让您感到恐惧,那么这里有一个完全合法的破解方法:

void myclass::delete_me()
{
    std::unique_ptr<myclass> bye_bye(this);
}

我认为这delete this是惯用的C ++,我只是出于好奇而提出。

在某些情况下,此构造实际上是有用的-在引发需要来自该对象的成员数据的异常后,可以删除该对象。该对象保持有效,直到发生抛出之后。

void myclass::throw_error()
{
    std::unique_ptr<myclass> bye_bye(this);
    throw std::runtime_exception(this->error_msg);
}

注意:如果您使用的编译器早于C ++ 11,则可以使用std::auto_ptr代替std::unique_ptr,它将执行相同的操作。


我无法使用c ++ 11进行编译,是否有一些特殊的编译器选项?还不需要移动this指针吗?
猫头鹰

@Owl不确定您的意思,它对我有用ideone.com/aavQUKunique_ptr另一个 创建一个unique_ptr需要移动,但不需要从原始指针移动。除非C ++ 17中的情况有所变化?
马克·兰瑟姆

啊C ++ 14,这就是为什么。我需要在开发箱上更新我的C ++。今晚我将在最近出现的gentoo系统上重试!
猫头鹰

25

设计C ++的原因之一是使其易于重用代码。通常,应该编写C ++,以便无论在堆,数组还是堆栈上实例化该类,它都可以工作。“删除此”是一种非常糟糕的编码实践,因为它仅在堆上定义了单个实例的情况下才起作用。而且最好不要再有另一个delete语句,大多数开发人员通常使用该语句来清理堆。这样做还假定将来没有维护程序员可以通过添加删除语句来解决错误感知的内存泄漏。

即使您事先知道您当前的计划是仅在堆上分配一个实例,但是如果将来出现一些乐高手的开发人员决定在堆栈上创建一个实例,该怎么办?或者,如果他将班级的某些部分剪切并粘贴到他打算在堆栈上使用的新班级,该怎么办?当代码到达“删除此内容”时,它将关闭并删除它,但是当对象超出范围时,它将调用析构函数。然后,析构函数将尝试再次将其删除,然后您将被清除。过去,执行此类操作不仅会破坏程序,还会导致操作系统和计算机重新启动。无论如何,强烈不建议这样做,几乎应避免这样做。我将不得不绝望,认真地贴上石膏,


7
+1。我不明白你为什么被拒绝。“应该编写C ++,以便无论在堆,数组还是堆栈上实例化该类都可以工作”是非常好的建议。
2012年

1
您可以将要删除自身的对象包装在一个特殊的类中,该类先删除该对象,然后再删除自身,然后使用此技术来防止堆栈分配:stackoverflow.com/questions/124880/…有时确实没有可行的选择。我只是使用此技术自删除了由DLL函数启动的线程,但是DLL函数必须在线程结束之前返回。
Felix Dombek

您不能以这样的方式进行编程,即某人仅用您的代码进行复制和粘贴最终还是会滥用它
Jimmy T.

22

它是允许的(之后不要使用该对象),但是我不会在实践中编写此类代码。我认为,delete this应该调用只出现在功能releaseRelease看起来像:void release() { ref--; if (ref<1) delete this; }


这对我的每个项目来说都是一次... :-)
cmaster-恢复莫妮卡2015年

15

好吧,在组件对象模型(COM)中,delete this构造可以是Release您要释放静态对象时调用的方法的一部分:

void IMyInterface::Release()
{
    --instanceCount;
    if(instanceCount == 0)
        delete this;
}

8

这是引用计数对象的核心习惯用法。

引用计数是确定性垃圾收集的一种强大形式-它确保对象管理其OWN生存期,而不是依赖于“智能”指针等来为它们执行此操作。仅可以通过“参考”智能指针访问基础对象,该智能指针的设计目的是使指针在实际对象中递增和递减成员整数(引用计数)。

当最后一个引用从堆栈中删除或被删除时,引用计数将为零。然后,对象的默认行为将是调用“删除此”以进行垃圾回收-我编写的库在基类中提供了一个受保护的虚拟“ CountIsZero”调用,以便您可以在缓存等操作中覆盖此行为。

确保这种安全的关键是不允许用户访问所讨论对象的CONSTRUCTOR(使其受到保护),而是使他们调用某些静态成员FACTORY,例如“ static Reference CreateT(...)”。这样,您就可以确定它们始终使用普通的“新”构建,并且永远都没有原始指针可用,因此“删除此”将永远不会崩溃。


您为什么不能只拥有一个(单个)类“分配器/垃圾收集器”,通过该接口完成所有分配并让该类处理分配的对象的所有引用计数?与其强迫对象自己去进行垃圾收集任务,还不如说与它们的指定目的无关。
伦丁2015年

1
您也可以使析构函数受保护,以禁止对对象进行静态和堆栈分配。
Game_Overture

7

您可以这样做。但是,您不能分配给它。因此,您陈述这样做的理由“我想改变看法”似乎非常可疑。我认为,更好的方法是将持有该视图的对象替换为该视图。

当然,您正在使用RAII对象,因此实际上根本不需要调用delete ...对吗?


4

这是一个古老的,已回答的问题,但是@Alexandre问“为什么有人要这样做?”,我想我可以提供一个今天下午正在考虑的用法示例。

旧版代码。在末尾使用裸指针Obj * obj。

不幸的是,我有时需要(而不是经常)使对象存活更长的时间。

我正在考虑使其成为参考计数的智能指针。但是,如果我要在任何地方使用,都会有很多代码需要更改ref_cnt_ptr<Obj>。如果将裸对象Obj *和ref_cnt_ptr混合使用,即使最后一个ref_cnt_ptr消失了,您也可以隐式删除对象,即使还有Obj *仍然存在。

所以我在考虑创建一个explicit_delete_ref_cnt_ptr。即引用计数的指针,其中仅在显式删除例程中进行删除。在现有代码知道对象生命周期的地方以及在我的新代码中使用它可以使对象存活更长的时间。

在对explicit_delete_ref_cnt_ptr进行操纵时,递增和递减引用计数。

但是,当在explicit_delete_ref_cnt_ptr析构函数中将参考计数视为零时,则不能释放。

仅当在类似删除的显式操作中看到参考计数为零时才释放。例如:

template<typename T> class explicit_delete_ref_cnt_ptr { 
 private: 
   T* ptr;
   int rc;
   ...
 public: 
   void delete_if_rc0() {
      if( this->ptr ) {
        this->rc--;
        if( this->rc == 0 ) {
           delete this->ptr;
        }
        this->ptr = 0;
      }
    }
 };

好,像这样 引用计数的指针类型没有自动删除rc'ed ptr析构函数中指向的对象是有点不寻常的。但是似乎这样会使混合裸指针和rc'ed指针更加安全。

但是到目前为止,不需要删除它。

但是后来我想到了:如果指向的对象pointee知道该对象正在被引用计数,例如,如果该计数位于对象内部(或其他表中),则例程delete_if_rc0可以是指针对象,而不是(智能)指针。

class Pointee { 
 private: 
   int rc;
   ...
 public: 
   void delete_if_rc0() {
        this->rc--;
        if( this->rc == 0 ) {
           delete this;
        }
      }
    }
 };

实际上,它根本不需要是成员方法,而可以是一个自由函数:

map<void*,int> keepalive_map;
template<typename T>
void delete_if_rc0(T*ptr) {
        void* tptr = (void*)ptr;
        if( keepalive_map[tptr] == 1 ) {
           delete ptr;
        }
};

(顺便说一句,我知道代码不太正确-如果添加所有细节,它的可读性就会降低,所以我将其保留为这样。)


0

只要对象在堆中,删除它是合法的。您需要要求对象只能是堆。唯一的方法是保护析构函数-这种删除只能从class调用,因此您需要一种可以确保删除的方法

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.