删除此&私人析构函数

时间:2012-09-07 11:03:49

标签: c++ oop memory this destructor

我一直在考虑在c ++中可能使用delete this,我看过一次使用。

因为只有当一个对象在堆上时才可以说delete this,我可以使析构函数变为私有,并完全阻止在堆栈上创建对象。最后,我可以通过在充当析构函数的随机公共成员函数中说delete this来删除堆上的对象。我的问题:

1)为什么我要强制在堆上而不是在堆栈上创建对象?

2)除此之外还有delete this的另一种用法吗? (假设这是合法使用它:))

9 个答案:

答案 0 :(得分:7)

任何使用delete this的方案都有点危险,因为无论是谁调用了这个函数,都会留下一个悬空指针。 (当然,当您正常删除对象时也是这种情况,但在这种情况下,很明显该对象已被删除)。然而,有些合法的案例需要一个对象来管理自己的生命周期。

它可以用来实现令人讨厌的,侵入式的引用计数方案。你将拥有“获得”的功能。对对象的引用,防止它被删除,然后"释放"一旦你完成了,如果没有其他人获得它,删除它,按照以下方式:

class Nasty {
public:
    Nasty() : references(1) {}

    void acquire() {
        ++references;
    }
    void release() {
        if (--references == 0) {
            delete this;
        }
    }
private:
    ~Nasty() {}
    size_t references;
};

// Usage
Nasty * nasty = new Nasty; // 1 reference
nasty->acquire();          // get a second reference
nasty->release();          // back to one
nasty->release();          // deleted
nasty->acquire();          // BOOM!

我更倾向于使用std::shared_ptr来实现此目的,因为它的线程安全,异常安全,适用于任何类型而无需任何显式支持,并且在删除后阻止访问。

更有用的是,它可以在事件驱动的系统中使用,在该系统中创建对象,然后管理自己,直到他们收到一个告诉他们不再需要它们的事件:

class Worker : EventReceiver {
public:
    Worker() {
        start_receiving_events(this);
    }    
    virtual void on(WorkEvent) {
        do_work();
    }
    virtual void on(DeleteEvent) {
        stop_receiving_events(this);
        delete this;
    }
private:
    ~Worker() {}
    void do_work();
};

答案 1 :(得分:4)

  

1)为什么我要强制在堆上而不是在堆栈上创建对象?

1)因为对象的生命周期在逻辑上与范围(例如,函数体等)无关。要么是因为它必须管理自己的生命周期,要么因为它本身就是一个共享对象(因此,它的生命周期必须附加到它的共同依赖对象的生命周期)。这里的一些人已经指出了一些例子,比如事件处理程序,任务对象(在调度程序中),以及复杂对象层次结构中的常规对象。

2)因为你想控制为分配/释放和构造/销毁执行代码的确切位置。这里的典型用例是跨模块代码(遍布可执行文件和DLL(或.so文件))。由于二进制兼容性问题和模块之间的单独堆,通常要求您严格控制这些分配构造操作发生在哪个模块中。这意味着仅使用基于堆的对象。

  

2)除此之外还有另外一种删除方法吗? (假设这是合法使用它:))

嗯,你的用例实际上只是一个“操作方法”而不是“为什么”。当然,如果您要在成员函数中使用delete this;语句,那么您必须有适当的控件来强制所有创建与new一起发生(并且与{相同的翻译单元)发生{1}}声明)。不这样做只会是非常非常糟糕的风格和危险。但这并没有解决你使用它的“原因”。

1)正如其他人所指出的,一个合法的用例是你有一个对象,可以确定它的工作何时结束并因此自行销毁。例如,事件处理程序在处理事件时删除自身,一旦指定完成事务就删除自身的网络通信对象,或者调度程序中的任务对象在任务完成时删除自身。然而,这留下了一个大问题:向外界发出信号,表明它不再存在。这就是为什么许多人提到了“侵入式引用计数”方案,这是确保仅在没有更多引用时删除对象的一种方法。另一种解决方案是使用“有效”对象的全局(类似单一)存储库,在这种情况下,对对象的任何访问都必须通过存储库中的检查,并且对象还必须在存储库中添加/删除自身时间,因为它进行新的和delete this;调用(作为重载的新/删除的一部分,或者与每个新的/删除调用一起)。

然而,实现相同的行为有一种更简单,更少侵入的方式,虽然不太经济。可以使用自引用delete this;方案。如此:

shared_ptr

通过上述(或根据您的需要对此粗略示例进行一些修改),只要对象认为必要并且没有其他人正在使用它,该对象将处于活动状态。弱指针机制充当代理,以通过对象的可能外部用户来查询对象的存在。此方案使对象更重(在其中有一个共享指针),但实现起来更容易,更安全。当然,你必须确保对象最终会自行删除,但在这种情况下这是给定的。

2)第二个用例我可以想到与限制一个对象仅限于堆的第二个动机(见上文),但是,它也适用于你不限制它的情况。如果要确保将释放和销毁都分派到正确的模块(从中分配和构造对象的模块),则必须使用动态分派方法。为此,最简单的方法是使用虚拟功能。但是,虚拟析构函数不会削减它,因为它只调度破坏而不是释放。解决方案是使用虚拟“destroy”函数,该函数在相关对象上调用class AutonomousObject { private: std::shared_ptr<AutonomousObject> m_shared_this; protected: AutonomousObject(/* some params */); public: virtual ~AutonomousObject() { }; template <typename... Args> static std::weak_ptr<AutonomousObject> Create(Args&&... args) { std::shared_ptr<AutonomousObject> result(new AutonomousObject(std::forward<Args>(args)...)); result->m_shared_this = result; // link the self-reference. return result; // return a weak-pointer. }; // this is the function called when the life-time should be terminated: void OnTerminate() { m_shared_this.reset( NULL ); // do not use reset(), but use reset( NULL ). }; }; 。这是一个实现这个目标的简单方案:

delete this;

上述类型的方案在实践中运行良好,并且它具有很好的优点,即类可以充当基类,而不会在派生类中通过此虚拟销毁机制进行额外的入侵。并且,您还可以修改它以仅允许基于堆的对象(通常,使构造函数 - 析构函数为私有或受保护)。没有基于堆的限制,优点是你仍然可以使用该对象作为局部变量或数据成员(按值),但是,当然,还有一些循环漏洞可以避免被任何人使用类。

据我所知,这些是我见过或听过的唯一合法用例(第一个很容易避免,正如我所展示的那样,而且经常应该这样)。

答案 2 :(得分:3)

一般原因是对象的生命周期由类内部的某个因素决定,至少从应用程序的角度来看。因此,它很可能是一个调用delete this;的私有方法。

显然,当对象是唯一知道需要多长时间的对象时,你不能把它放在一个随机的线程堆栈上。有必要在堆上创建这样的对象。

答案 3 :(得分:1)

这通常是一个非常糟糕的主意。有很少的情况 - 例如,COM对象强制执行侵入式引用计数。你只会用非常具体的情境理由来做这件事 - 永远不要用于通用课程。

答案 4 :(得分:1)

  

1)为什么我要强制在堆上而不是在堆栈上创建对象?

因为其生命周期不是由范围规则决定的。

  

2)除此之外还有另外一种删除方法吗? (假设这是合法使用它:))

当对象是最适合其自身寿命的对象时,使用delete this。我所知道的最简单的例子之一是GUI中的窗口。窗口对事件做出反应,其中一个子集意味着窗口必须关闭然后被删除。在事件处理程序中,窗口执行delete this。 (您可以将处理委托给控制器类。但是“窗口转发事件到控制器类决定删除窗口的情况”与delete this没有太大区别,窗口事件处理程序将保留窗口您可能还需要将结束与删除分离,但您的理由与delete this}的可取性无关。

答案 5 :(得分:1)

delete this;

有时很有用,通常用于控制另一个对象生命周期的控件类。对于侵入式引用计数,它所控制的类是从它派生的类。

使用这样一个类的结果应该是让您的班级的用户或创作者更容易处理生命周期。如果没有实现这一点,那就不好了。

一个合法的例子可能是你需要一个类在它被破坏之前清除对它自己的所有引用。在这种情况下,无论何时存储对它的引用(在模型中,大概是),你都要“告诉”类,然后在退出时,你的类会在它调用delete this之前将这些引用或其他任何内容归零。本身。

对于班级用户来说,这应该都是“幕后”。

答案 6 :(得分:1)

“为什么我要强制在堆上而不是在堆栈上创建对象?”

通常当你强迫它不是因为你想要这样时,这是因为该类是某些多态层次结构的一部分,而得到一个的唯一合法方法是来自返回的工厂函数根据您传递的参数或根据其了解的某些配置,派生类的实例。然后很容易安排工厂函数使用new创建它们。即使他们想要,这些类的用户也无法将它们放在堆栈中,因为他们事先并不知道他们正在使用的对象的派生类型,只知道基类型。

一旦有了这样的对象,就会知道它们已经被delete破坏了,你可以考虑以最终以delete this结尾的方式管理它们的生命周期。如果对象以某种方式能够知道何时不再需要它,通常会(如迈克所说),因为它是某个框架的一部分,它不能明确地管理对象生存期,但它确实告诉其组件他们已被分离/取消注册/无论[*]。

如果我没记错的话,James Kanze就是你的男人。我可能错误地记得,但我认为他偶尔会提到他的设计中delete this不仅仅是使用而且很常见。这样的设计避免了共享所有权和外部生命周期管理,有利于管理自己生命周期的实体对象网络。并且在必要时,在摧毁自己之前,将自己从任何了解它们的东西中注销。因此,如果你在“工具带”中有几个“工具”,那么你就不会解释为工具带“拥有”对每个工具的引用,你会想到工具将自己放入带内外。

[*]否则,您的工厂将返回unique_ptrauto_ptr,以鼓励来电者将对象直接填充到他们选择的内存管理类型中,或者您返回原始内容指针,但通过文档提供相同的鼓励。你习惯看到的所有东西。

答案 7 :(得分:0)

一个好的经验法则是不要使用delete this

简单地说,使用new的东西应该足够负责在完成对象时使用delete。这也避免了堆栈/堆上的问题。

答案 8 :(得分:0)

曾几何时我正在编写一些插件代码。我相信我混合构建(插件调试,主代码发布或者反过来)因为一部分应该很快。或者可能发生了另一种情况这个main已经在gcc上发布,插件正在VC上调试/测试。当主代码从插件或插件中删除某些东西时会发生内存问题。这是因为他们都使用了不同的内存池或malloc实现。所以我有一个私有的dtor和一个名为deleteThis()的虚函数。

-edit-现在我可以考虑重载删除操作符或使用智能指针,或者只是简单地说明从不删除函数。除非你真的知道你在做什么(不要这样做),否则它将依赖并且通常会超载new / delete。我决定使用deleteThis()因为我发现它更容易然后像thing_alloc和thing_free这样的C方式就像deleteThis()感觉更像是OOP的方式