19

在这个SO question中指出,这种结构可以防止实例的堆栈分配。

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

我的问题是,它如何防止分配?我了解,无论是显式还是隐式,都无法删除此实例。但我认为,这将分别导致内存泄漏或运行时错误。

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

4

6 回答 6

36

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

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

于 2013-09-17T10:47:35.010 回答
20

我了解,无论是显式还是隐式,都无法删除此实例。

不仅如此,不可能销毁任何实例;无论是通过删除还是其他方式。

声明一个自动变量(或“堆栈分配”,如果你愿意的话)不仅会在程序到达声明点时创建实例;当程序离开该块时,它也会导致它被销毁。使用已删除的析构函数,这是无法完成的,因此不允许声明。

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

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

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

还有为什么需要这个?

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

于 2013-09-17T11:07:57.730 回答
6

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

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

于 2013-09-17T10:47:30.330 回答
5

我有两个答案:


来自“C++ 编程语言”:

不能有一个不能被破坏的局部变量(§17.2.2)......


用我自己的话:

想一想 - 在堆栈上创建的变量意味着在声明它的范围结束时被删除。

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

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

于 2013-09-18T00:32:08.237 回答
3

当您尝试在堆栈上声明 FS_Only

{ 
    FS_Only fs;
    //...
}

析构函数将自动在右大括号上调用,因此您将收到编译错误。
当然,你也不能'ed 一个deletenew

{ 
    FS_Only * fs = new FS_Only();
    //...
    delete fs; //also won't compile
}
于 2013-09-17T10:50:32.103 回答
0

分配此类对象的唯一方法是通过动态分配,无需任何解除分配(该对象将被泄露,或在程序执行期间被指针访问。

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

于 2013-09-17T10:49:55.743 回答