在这个SO question中指出,这种结构可以防止实例的堆栈分配。
class FS_Only {
~FS_Only() = delete; // disallow stack allocation
};
我的问题是,它如何防止分配?我了解,无论是显式还是隐式,都无法删除此实例。但我认为,这将分别导致内存泄漏或运行时错误。
编译器是否足够聪明,可以解决这个问题并引发编译器错误?还有为什么需要这个?
在这个SO question中指出,这种结构可以防止实例的堆栈分配。
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;
//...
}
析构函数将自动在右大括号上调用,因此您将收到编译错误。
当然,你也不能'ed 一个delete
:new
{
FS_Only * fs = new FS_Only();
//...
delete fs; //also won't compile
}
分配此类对象的唯一方法是通过动态分配,无需任何解除分配(该对象将被泄露,或在程序执行期间被指针访问。
当分配的对象超出范围时(因为无法调用析构函数代码),任何分配静态实例的尝试都会导致编译错误。