是否允许删除?

时间:2010-06-30 15:45:55

标签: c++ memory-management new-operator delete-operator self-destruction

如果delete-statement是将在该类实例上执行的最后一个语句,是否允许delete this;?当然,我确信this指针所代表的对象是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;
}

我可以这样做吗?

10 个答案:

答案 0 :(得分:221)

C ++ FAQ Lite有一个专门针对此

的条目

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

  

只要你小心,对象就可以自杀(删除它)。

答案 1 :(得分:77)

是的,delete this;已经定义了结果,只要(正如您所注意到的)您确保对象是动态分配的,并且(当然)从不尝试在对象被销毁后使用它。多年来,关于标准对delete this;的具体说明,而不是删除其他指针,已经提出了许多问题。答案是相当简短的:它并没有说太多。它只是说delete的操作数必须是一个表达式,指定一个对象或一个对象数组的指针。它详细介绍了如何计算调用释放内存的释放函数(如果有的话),但是delete(§[expr.delete])上的整个部分没有具体提到delete this;。关于析构函数的部分确实在一个地方提到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 :(得分:45)

如果它让你害怕,那就是完全合法的黑客攻击:

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,它会执行相同的操作。

答案 3 :(得分:22)

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

即使你事先知道你当前的计划是只在堆上分配一个实例,如果将来会有一些快乐的开发人员出现并决定在堆栈上创建一个实例呢?或者,如果他将类的某些部分剪切并粘贴到他打算在堆栈中使用的新类,该怎么办?当代码到达“删除它”时,它将关闭并删除它,但是当对象超出范围时,它将调用析构函数。析构函数然后会尝试再次删除它,然后你就被软管了。在过去,做这样的事情不仅会搞砸程序,而且还需要重新启动操作系统和计算机。无论如何,这是非常不推荐的,几乎总是应该避免。我必须绝望,严肃地涂抹,或者真的讨厌我为编写代码而工作的公司。

答案 4 :(得分:19)

允许(之后不要使用该对象),但我不会在练习中编写这样的代码。我认为delete this只应出现在调用releaseRelease的函数中,并显示为:void release() { ref--; if (ref<1) delete this; }

答案 5 :(得分:13)

好吧,在组件对象模型(COM)中delete this构造可以是Release方法的一部分,只要你想释放被获取的对象就会调用它:

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

答案 6 :(得分:7)

你可以这样做。但是,您无法分配给此。因此,您声明这样做的原因,“我想改变观点”,似乎非常值得怀疑。在我看来,更好的方法是保持视图的对象替换该视图。

当然,你正在使用RAII对象,所以你根本不需要调用删除...对吗?

答案 7 :(得分:7)

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

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

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

使这个安全的关键是不允许用户访问相关对象的CONSTRUCTOR(使其受保护),而是让他们调用一些静态成员 - 类似于FACTORY的“静态参考CreateT(...) ”。这样你就知道它们总是用普通的“新”构建,并且没有原始指针可用,所以“删除它”不会爆炸。

答案 8 :(得分:3)

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

旧版代码。使用裸指针Obj * 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指针更加安全。

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

然后它发生在我身上:如果对象指向,指针对象,知道它被引用计数,例如如果计数在对象内(或在其他表中),则例程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;
        }
};

(顺便说一句,我知道代码不太正确 - 如果我添加所有细节,它变得不那么可读,所以我就这样离开了。)

答案 9 :(得分:0)

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