提问人:beetlej 提问时间:12/16/2016 更新时间:12/16/2016 访问量:9836
析构函数中的 C++ 异常
c++ exception in destructor
问:
从其他线程中,我知道我们不应该在析构函数中抛出异常!但对于下面的例子,它确实有效。这是否意味着我们只能在一个实例的析构函数中抛出异常?我们应该如何理解这个代码示例!
#include <iostream>
using namespace std;
class A {
public:
~A() {
try {
printf("exception in A start\n");
throw 30;
printf("exception in A end\n");
}catch(int e) {
printf("catch in A %d\n",e);
}
}
};
class B{
public:
~B() {
printf("exception in B start\n");
throw 20;
printf("exception in B end\n");
}
};
int main(void) {
try {
A a;
B b;
}catch(int e) {
printf("catch in main %d\n",e);
}
return 0;
}
输出为:
exception in B start
exception in A start
catch in A 30
catch in main 20
答:
“我们不应该在析构函数中抛出异常”的建议不是绝对的。问题在于,当抛出异常时,编译器开始展开堆栈,直到找到该异常的处理程序。展开堆栈意味着为那些因为堆栈帧正在消失而消失的对象调用析构函数。如果其中一个析构函数抛出未在析构函数本身中处理的异常,则会发生此建议所讨论的问题。如果发生这种情况,程序会调用,有些人认为这种情况发生的风险非常严重,以至于他们必须编写编码指南来防止这种情况发生。std::terminate()
在您的代码中,这不是问题。的析构函数抛出异常;因此,还调用了析构函数。该析构函数引发异常,但在析构函数内部处理异常。所以没有问题。B
a
如果更改代码以删除析构函数中的块,则析构函数中抛出的异常不会在析构函数中处理,因此最终会调用 。try ... catch
A
std::terminate()
编辑:正如 Brian 在他的回答中指出的那样,这条规则在 C++11 中发生了变化:析构函数是隐式的,所以你的代码应该在对象被销毁时调用。将析构函数标记为“修复”此问题。noexcept
terminate
B
noexcept(false)
评论
C++17 之前的最佳实践是不要让异常从析构函数中传播出来。如果析构函数包含表达式或调用可能引发的函数,则很好,只要捕获并处理抛出的异常,而不是从析构函数中转义即可。所以你很好。throw
A::~A
在 的情况下,您的程序在 C++03 中很好,但在 C++11 中就不行了。规则是,如果您确实让异常从析构函数中传播出来,并且该析构函数用于通过堆栈展开直接销毁的自动对象,则将调用该析构函数。由于在堆栈展开过程中不会被销毁,因此将捕获从中抛出的异常。但是在 C++11 中,析构函数将被隐式声明,因此,允许异常从中传播出去将无条件调用。B::~B
std::terminate
b
B::~B
B::~B
noexcept
std::terminate
要允许在 C++11 中捕获异常,您可以编写
~B() noexcept(false) {
// ...
}
尽管如此,还是会出现一个问题,即在堆栈展开期间可能被调用---在这种情况下,将被调用。由于在 C++17 之前,无法判断是否是这种情况,因此建议永远不要让异常从析构函数中传播出去。遵循这个规则,你会没事的。B::~B
std::terminate
在 C++17 中,可用于检测在堆栈展开期间是否正在销毁对象。但你最好知道你在做什么。std::uncaught_exceptions()
评论
终止
,这一事实完全证实了这一点。在这种情况下,g++ 6.2 甚至给出了一个很好的警告“警告:throw 将始终调用 terminate() [-Wterminate] 注意:在 C++11 析构函数中默认为 noexcept”。
上一个:什么是三法则?
评论
throw 42;
B b;
terminate called after throwing an instance of 'int'
<iostream>
printf