显式调用析构函数
Posted
技术标签:
【中文标题】显式调用析构函数【英文标题】:Explicit call to destructor 【发布时间】:2019-04-15 04:19:40 【问题描述】:我偶然发现了以下代码sn-p:
#include <iostream>
#include <string>
using namespace std;
class First
string *s;
public:
First() s = new string("Text");
~First() delete s;
void Print() cout<<*s;
;
int main()
First FirstObject;
FirstObject.Print();
FirstObject.~First();
文中说这个sn-p应该会导致运行时错误。现在,我不太确定,所以我尝试编译并运行它。有效。奇怪的是,尽管所涉及的数据很简单,但程序在打印“文本”后卡顿,仅在一秒钟后完成。
我添加了一个要打印到析构函数的字符串,因为我不确定显式调用这样的析构函数是否合法。该程序打印了两次字符串。所以我的猜测是析构函数被调用了两次,因为正常的程序终止不知道显式调用并尝试再次销毁对象。
简单的搜索证实,在自动化对象上是危险的,因为第二次调用(当对象超出范围时)具有未定义的行为。所以我对我的编译器(VS 2017)或这个特定程序很幸运。
关于运行时错误的文字是否完全错误?或者运行时错误真的很常见吗?或者也许我的编译器针对这种事情实现了某种防护机制?
【问题讨论】:
C++ 标准从不保证运行时错误(总是未定义的行为),所以文本肯定是错误的 @UnholySheep,我不知道我会这么说。例如,离开noexcept
函数的异常是对 std::terminate
的保证调用,我将其归类为运行时错误。
@chris 好点,我只是在考虑所描述的情况(而且我看到太多的文本声称调用 UB 的代码总是会导致运行时错误/分段错误)
如何将字符串打印两次?它将释放它两次,因为当对象被销毁时会调用析构函数。
运行时错误肯定会发生:析构函数被调用了两次,这是一个错误,它发生在运行时。我敢打赌,如果您在调试模式下运行测试,您可以“捕捉”它。为什么没有消息弹出?知道它所做的最后两件事是删除同一个指针时,VC2017 在终止您的应用程序时会在发布模式下做什么?是否有一些优化会错误地隐藏/修复您的错误?您可能应该询问 MS 支持...
【参考方案1】:
简单的搜索证实,在自动化对象上显式调用析构函数是危险的,因为第二次调用(当对象超出范围时)具有未定义的行为。
确实如此。如果您使用自动存储显式销毁对象,则会调用未定义的行为。 Learn more about it.
所以我很幸运能够使用我的编译器(VS 2017)或这个特定的程序。
我会说你是不幸的。 UB 可能发生的最好的(对你来说,编码器)是在第一次运行时崩溃。如果它看起来工作正常,那么崩溃可能会在 2038 年 1 月 19 日在生产中发生。
关于运行时错误的文字是否完全错误?或者运行时错误真的很常见吗?或者也许我的编译器针对这种事情实现了某种防护机制?
是的,文字有点错误。 未定义的行为是未定义的。运行时错误只是众多可能性之一(包括鼻恶魔)。
关于未定义行为的好读物:What is undefined behavor?
【讨论】:
"当您使用自动存储显式销毁对象时调用未定义行为" - 更像是在对象到达其自然生命周期结束而没有被转世时调用它。析构函数调用本身不是 UB。 更准确地说,2038 年 1 月 19 日 03:14:08 UTC :D @StoryTeller 是的。我想避免细节。我已经修好了。【参考方案2】:关于运行时错误的文字是否完全错误?
错了。
或者运行时错误真的很常见吗?或者也许我的编译器针对这种事情实现了某种防护机制?
你不知道,当你的代码调用Undefined Behavior时会发生这种情况;你不知道执行它时会发生什么。
在你的情况下,你是(不)幸运*并且它起作用了,而对我来说,它导致了error(双倍免费)。
*因为如果您收到错误,您将开始调试,否则,例如在大型项目中,您可能会错过它...
【讨论】:
【参考方案3】:不,这只是 C++ 标准草案[class.dtor]p16 中未定义的行为:
一旦为对象调用析构函数,该对象就不再存在;如果为生命周期已结束的对象([basic.life])调用析构函数,则行为未定义。 [ 示例:如果自动对象的析构函数被显式调用,并且随后以通常会调用对象的隐式销毁的方式留下块,则行为未定义。 —— 结束示例
我们可以从defintion of undefined behavior看到:
本文档没有要求的行为
您不能对结果抱有任何期望。对于作者而言,在特定机器上使用特定选项的特定编译器可能会以这种方式表现,但我们不能指望它是可移植的或可靠的结果。虽然在某些情况下implementation does try to obtain a specific result 但这只是另一种可接受的未定义行为。
另外,[class.dtor]p15 提供了我上面引用的规范部分的更多背景信息:
[ 注意:很少需要显式调用析构函数。 这种调用的一种用途是使用放置新表达式放置在特定地址的对象。 为了处理专用硬件资源和编写内存管理工具,可能需要使用对象的显式放置和销毁。 例如,
void* operator new(std::size_t, void* p) return p; struct X X(int); ~X(); ; void f(X* p); void g() // rare, specialized use: char* buf = new char[sizeof(X)]; X* p = new(buf) X(222); // use buf[] and initialize f(p); p->X::~X(); // cleanup
— 尾注 ]
【讨论】:
以上是关于显式调用析构函数的主要内容,如果未能解决你的问题,请参考以下文章