析构函数上的 =delete 如何阻止堆栈分配



在这个SO问题中,说明此构造阻止了实例的堆栈分配。

class FS_Only {
    ~FS_Only() = delete;  // disallow stack allocation
};

我的问题是,它如何防止分配?我知道,不可能显式或隐式地删除此实例。但我认为,这将分别导致内存泄漏或运行时错误。

编译器

是否足够聪明来解决这个问题并引发编译器错误?还有为什么需要这个?

具有

自动存储持续时间的变量(即局部变量)的析构函数需要在变量的生存期结束时运行。如果没有可访问的析构函数,编译器将拒绝编译分配此类变量的代码。

基本上,"堆栈分配"(顺便说一下,这是一个不准确的术语选择)和自由存储分配之间的区别在于,使用局部变量构造函数/析构函数调用总是成对出现,而对于自由存储分配,您可以在不破坏它的情况下构造对象。因此,通过阻止对析构函数的访问,您的代码使得无法分配该类型的局部变量(如果构造函数运行析构函数也必须运行,但没有析构函数,因此程序被拒绝)。

我知道,不可能显式或隐式地删除此实例。

更重要的是,不可能销毁任何实例;无论是通过删除还是其他方式。

声明

一个自动变量(或"堆栈分配",如果你愿意的话)不仅会导致在程序到达声明点时创建实例;它还会导致它在程序离开该块时被销毁。对于已删除的析构函数,无法执行此操作,因此不允许声明。

这还可以防止您声明静态或线程局部变量,因为这还会生成代码以在程序或线程结束时销毁变量。

因此,创建其中之一的唯一方法是 new ,一旦完成,您将永远无法破坏它。但是,这并不能完全阻止堆栈分配:

char memory[sizeof(FS_Only)] alignas(FS_Only);
FS_Only * not_fs = new (memory) FS_Only;

还有为什么需要这个?

在我看来,你不会。强制内存泄漏是确保对象永远不会在错误的时间被销毁的可怕方法。相反,请使用 RAII 等技术来管理任何需要动态生存期的对象,确保它们始终具有定义明确的所有者(或共享所有者)负责在使用后删除它们。C++11 标准库中的智能指针是一个很好的起点。

将析构函数标记为已删除将导致无法销毁对象。它是在堆栈上还是在堆上都没有关系。对象的所有销毁(无论是通过超出范围自动执行,还是通过对其执行delete)调用析构函数。由于它是处理析构函数调用的编译器,它会注意到它是否被标记为已删除并发出错误。

但是

,它并不禁止创建对象,但是由于非指针实例在声明它们的范围的末尾被破坏,编译器将发出错误,实际上不允许创建实例。仍然可以使用 new 动态分配实例,但需要注意的是它们不能被删除,这可能会导致内存泄漏。

我有两个答案:


来自"C++编程语言":

不能拥有无法销毁的局部变量 (§17.2.2) ...


用我自己的话说:

考虑一下 - 在堆栈上创建的变量应该在声明它的作用域结束时删除。

通过禁止对象的隐式析构函数,您基本上是在告诉编译器"不允许在作用域结束时删除此对象"。编译器不想冒险创建无法销毁的东西(这是对的 - 如果它很大并且会泄漏兆字节的内存怎么办?),所以它拒绝创建它。这只剩下一个选择 - 在免费商店中创建它并手动管理它。

现在责任在你了。但是请注意,这是错误的代码 - 对象永远无法delete d,即使是手动的。记住 - 你delete析构函数!正如在其他答案中正确指出的那样,这是强制内存泄漏,应避免。

当您尝试在堆栈上声明FS_Only时

{ 
    FS_Only fs;
    //...
}

析构函数将在右大括号上自动调用,因此您将收到编译错误。
当然,您也无法delete new

的:
{ 
    FS_Only * fs = new FS_Only();
    //...
    delete fs; //also won't compile
}

分配此类对象的唯一方法是通过动态分配,而无需任何取消分配(对象将被泄漏,或在程序持续时间内通过指针访问。

当分配的对象超出

范围时,任何分配静态实例的尝试都会导致编译错误(因为无法调用析构函数代码)。

最新更新