249

delete this;如果删除语句是将在该类的实例上执行的最后一条语句,是否允许?当然,我确定由this-pointer 表示的对象是newly-created 的。

我正在考虑这样的事情:

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;
}

我可以这样做吗?

4

10 回答 10

256

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

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

只要你小心,一个对象自杀是可以的(删除这个)。

于 2010-06-30T15:47:37.287 回答
95

是的,delete this;已经定义了结果,只要(正如您所指出的)您确保对象是动态分配的,并且(当然)永远不要在对象被销毁后尝试使用它。多年来,人们提出了许多关于标准具体说明的问题delete this;,而不是删除其他一些指针。答案相当简短:它什么也没说。它只是说delete' 的操作数必须是一个表达式,它指定一个指向对象的指针,或者一个对象数组。它涉及到相当多的细节,比如它如何计算出调用什么(如果有的话)释放函数来释放内存,但是delete(§[expr.delete]) 的整个部分根本没有delete this;具体提及。关于析构函数的部分确实提到了delete this在一处(§[class.dtor]/13):

在虚拟析构函数的定义点(包括隐式定义(15.8)),非数组释放函数被确定为表达式 delete this 出现在析构函数类的非虚拟析构函数中(参见 8.3.5 )。

这倾向于支持标准认为delete this;有效的观点——如果它是无效的,那么它的类型就没有意义了。delete this;据我所知,这是标准中唯一提到的地方。

无论如何,有些人认为这是delete this一种令人讨厌的黑客行为,并告诉任何愿意倾听的人应该避免这种行为。一个常见的问题是难以确保类的对象只被动态分配。其他人认为这是一个完全合理的成语,并且一直使用它。就个人而言,我介于两者之间:我很少使用它,但当它似乎是适合这项工作的工具时,请毫不犹豫地使用它。

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

对于任何可能关心这种编码的可靠性的人:如果您向欧洲几乎任何地方拨打电话、从欧洲拨打电话或通过欧洲任何地方拨打电话,很有可能(至少部分)由代码处理正是这样做的。

于 2010-06-30T15:50:58.023 回答
49

如果它吓到你,有一个完全合法的黑客:

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,它会做同样的事情。

于 2010-06-30T15:56:10.043 回答
26

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

即使您事先知道您当前的计划是只在堆上分配一个实例,但如果将来某个幸运的开发人员出现并决定在堆栈上创建一个实例怎么办?或者,如果他将类的某些部分剪切并粘贴到他打算在堆栈上使用的新类中怎么办?当代码到达“delete this”时,它将关闭并删除它,但是当对象超出范围时,它将调用析构函数。然后析构函数将尝试再次删除它,然后你就被淹没了。在过去,做这样的事情不仅会搞砸程序,而且需要重新启动操作系统和计算机。在任何情况下,强烈不建议这样做,并且几乎总是应该避免这样做。我将不得不绝望,严重贴满,

于 2012-06-24T01:42:27.323 回答
23

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

于 2010-06-30T16:19:23.917 回答
17

好吧,在组件对象模型 (COM)delete this中,构造可以是Release方法的一部分,每当您想要释放获取的对象时都会调用该方法:

void IMyInterface::Release()
{
    --instanceCount;
    if(instanceCount == 0)
        delete this;
}
于 2010-06-30T16:30:05.700 回答
8

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

引用计数是确定性垃圾收集的一种强大形式——它确保对象管理它们的 OWN 生命周期,而不是依赖“智能”指针等来为它们做这件事。底层对象只能通过“引用”智能指针访问,其设计目的是使指针递增和递减实际对象中的成员整数(引用计数)。

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

使其安全的关键是不允许用户访问相关对象的 CONSTRUCTOR(使其受到保护),而是让他们调用一些静态成员 - FACTORY - 像“静态引用 CreateT(...)”。这样你就可以确定它们总是用普通的“new”构建的,并且没有原始指针可用,所以“delete this”永远不会爆炸。

于 2013-12-20T05:04:57.540 回答
7

你可以这样做。但是,您不能分配给它。因此,您说这样做的原因“我想改变观点”似乎很值得怀疑。在我看来,更好的方法是让持有该视图的对象替换该视图。

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

于 2010-06-30T16:19:08.237 回答
4

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

遗留代码。使用裸指针 Obj*obj,最后带有一个 delete 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 指针更安全一些。

但到目前为止没有必要删除它。

但后来我突然想到:如果指向的对象(指针对象)知道它正在被引用计数,例如,如果计数在对象内部(或在某个其他表中),那么例程 delete_if_rc0 可能是pointee 对象,而不是(智能)指针。

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;
        }
};

(顺便说一句,我知道代码不太正确——如果我添加所有细节,它的可读性就会降低,所以我就这样离开了。)

于 2012-04-27T22:29:10.543 回答
0

只要对象在堆中,删除它是合法的。您只需要要求对象是堆。做到这一点的唯一方法是使析构函数受保护 - 这样删除只能从类中调用,因此您需要一个确保删除的方法

于 2016-10-04T06:24:50.470 回答