如何在析构函数上删除=阻止分配?

时间:2013-09-17 10:45:15

标签: c++ c++11 delete-operator

在此SO question中声明此构造可防止实例的堆栈分配。

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

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

编译器是否足够聪明,可以对此进行排序并引发编译错误?还有为什么需要这个呢?

6 个答案:

答案 0 :(得分:24)

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

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

答案 1 :(得分:15)

  

我理解,无法显式或隐式删除此实例。

更重要的是,销毁任何实例都是不可能的;是否删除它。

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

这也会阻止你声明一个静态或线程局部变量,因为它还会在程序或线程结束时生成用于销毁变量的代码。

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

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

为什么还需要这个?

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

答案 2 :(得分:5)

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

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

答案 3 :(得分:3)

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

{ 
    FS_Only fs;
    //...
}

析构函数将自动在结束括号上调用,因此您将收到编译错误。
当然,您将无法delete new'ed ed:

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

答案 4 :(得分:3)

我有两个答案:


来自“The C ++ Programming Language”:

  

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


用我自己的话说:

考虑一下 - 在堆栈上创建的变量意味着在声明它的范围的末尾被删除。

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

现在责任在于你。但请注意,这是错误的代码 - 对象将永远能够delete d,甚至手动。记住 - 你delete ddes destructor!正如在其他答案中正确指出的那样,这是强制内存泄漏,应该避免。

答案 5 :(得分:0)

分配这样一个对象的唯一方法是通过动态分配,而不进行任何去位(对象将被泄露,或者在程序持续时间内被指针访问。

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