1) 为什么我要强制对象在堆上而不是在堆栈上?
1) 因为对象的生命周期在逻辑上与范围无关(例如,函数体等)。要么因为它必须管理自己的生命周期,要么因为它本质上是一个共享对象(因此,它的生命周期必须附加到它的共同依赖对象的生命周期)。这里有些人指出了一些例子,比如事件处理程序、任务对象(在调度程序中),以及复杂对象层次结构中的一般对象。
2) 因为您想控制代码在分配/释放和构造/销毁中执行的确切位置。这里的典型用例是跨模块代码(跨可执行文件和 DLL(或 .so 文件)传播)。由于模块之间的二进制兼容性和单独堆的问题,通常要求您严格控制这些分配构造操作发生在哪个模块中。这意味着仅使用基于堆的对象。
2) 除此以外,delete this 还有其他用途吗? (假设这是对它的合法使用:))
嗯,您的用例实际上只是“操作方法”而不是“为什么”。当然,如果你打算在成员函数中使用delete this; 语句,那么你必须有适当的控制来强制所有的创建以new 发生(并且在与delete this; 语句相同的翻译单元中发生)。不这样做只会是非常糟糕的风格和危险的。但这并没有解决您要使用它的“原因”。
1) 正如其他人所指出的,一个合法的用例是您拥有一个对象,该对象可以确定其工作何时结束并因此自行销毁。例如,事件处理程序在事件被处理时删除自己,网络通信对象在指定执行的事务结束后删除自己,或者调度程序中的任务对象在任务完成时删除自己。然而,这留下了一个大问题:向外界发出它不再存在的信号。这就是为什么许多人提到“侵入式引用计数”方案,这是确保对象仅在没有更多引用时才被删除的一种方法。另一种解决方案是使用“有效”对象的全局(类似单例)存储库,在这种情况下,对对象的任何访问都必须通过存储库中的检查,并且对象还必须同时从存储库中添加/删除自身进行 new 和 delete this; 调用的时间(作为重载的 new/delete 的一部分,或与每个 new/delete 调用一起)。
但是,有一种更简单且侵入性更小的方法可以实现相同的行为,尽管不太经济。可以使用自引用shared_ptr 方案。因此:
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 ).
};
};
通过上述(或这个粗略的例子的一些变化,取决于你的需要),只要它认为有必要并且没有其他人使用它,对象就会一直存在。弱指针机制充当代理,由对象的可能外部用户查询对象的存在。这种方案使对象更重(其中有一个共享指针),但实现起来更容易、更安全。当然,您必须确保对象最终会自行删除,但在这种情况下,这是给定的。
2) 我可以想到的第二个用例与将对象限制为仅堆的第二个动机有关(见上文),但是,它也适用于您不对其进行限制的情况。如果要确保释放和销毁都被分派到正确的模块(分配和构造对象的模块),则必须使用动态分派方法。为此,最简单的方法就是使用虚函数。但是,虚拟析构函数不会将其切断,因为它只调度销毁,而不是释放。解决方案是使用虚拟“销毁”函数,在相关对象上调用delete this;。这是实现此目的的简单方案:
struct CrossModuleDeleter; //forward-declare.
class CrossModuleObject {
private:
virtual void Destroy() /* final */;
public:
CrossModuleObject(/* some params */); //constructor can be public.
virtual ~CrossModuleObject() { }; //destructor can be public.
//.... whatever...
friend struct CrossModuleDeleter;
template <typename... Args>
static std::shared_ptr< CrossModuleObject > Create(Args&&... args);
};
struct CrossModuleDeleter {
void operator()(CrossModuleObject* p) const {
p->Destroy(); // do a virtual dispatch to reach the correct deallocator.
};
};
// In the cpp file:
// Note: This function should not be inlined, so stash it into a cpp file.
void CrossModuleObject::Destroy() {
delete this;
};
template <typename... Args>
std::shared_ptr< CrossModuleObject > CrossModuleObject::Create(Args&&... args) {
return std::shared_ptr< CrossModuleObject >( new CrossModuleObject(std::forward<Args>(args)...), CrossModuleDeleter() );
};
上述方案在实践中运行良好,它的优点是该类可以充当基类,而不会受到派生类中这种虚拟销毁机制的额外入侵。而且,您还可以修改它以仅允许基于堆的对象(通常使构造函数-析构函数私有或受保护)。如果没有基于堆的限制,优点是您仍然可以根据需要将对象用作局部变量或数据成员(按值),但是,当然,任何使用类。
据我所知,这些是我在任何地方见过或听说过的唯一合法用例(第一个很容易避免,正如我所展示的,而且经常应该避免)。