bee*_*lej 5 c++ exception-handling
从其他线程,我知道我们不应该在析构函数中抛出异常!但是对于下面的例子,它确实有效.这是否意味着我们只能在一个实例的析构函数中抛出异常?我们该如何理解这个代码示例!
#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;
}
Run Code Online (Sandbox Code Playgroud)
输出是:
exception in B start
exception in A start
catch in A 30
catch in main 20
Run Code Online (Sandbox Code Playgroud)
Bri*_*ian 16
C++ 17之前的最佳实践表示不要让异常从析构函数中传播出来.如果析构函数包含throw
表达式或调用可能抛出的函数,只要抛出异常被捕获并处理而不是从析构函数中转义,这是很好的.所以你A::~A
很好.
在这种情况下B::~B
,您的程序在C++ 03中很好,但在C++ 11中却没有.规则是,如果你确实让一个异常从析构函数传播出来,并且析构函数是一个被堆栈展开直接销毁的自动对象,那么std::terminate
就会被调用.因为b
作为堆栈展开的一部分没有被销毁,所以抛出的异常B::~B
将被捕获.但是在C++ 11中,B::~B
析构函数将被隐式声明noexcept
,因此,允许异常传播出来将std::terminate
无条件地调用.
要允许在C++ 11中捕获异常,您可以编写
~B() noexcept(false) {
// ...
}
Run Code Online (Sandbox Code Playgroud)
仍然存在可能B::~B
在堆栈展开期间被调用的问题- 在这种情况下,std::terminate
将被调用.因为,在C++ 17之前,无法判断是否是这种情况,建议绝不允许异常传播出析构函数.遵循这条规则,你会没事的.
在C++ 17中,可以使用std::uncaught_exceptions()
在堆栈展开期间检测对象是否被破坏.但你最好知道你在做什么.
"我们不应该在析构函数中抛出异常"的建议并不是绝对的.问题是当抛出异常时,编译器开始展开堆栈,直到它找到该异常的处理程序.展开堆栈意味着调用析构函数来处理因为堆栈框架消失而丢失的对象.如果其中一个析构函数抛出了一个未在析构函数本身内处理的异常,则会出现此建议的内容.如果发生这种情况,程序会调用std::terminate()
,有些人认为发生这种情况的风险非常严重,以至于他们必须编写编码指南来防止它.
在您的代码中,这不是问题.析构函数B
抛出异常; 结果,析构函数a
也被称为.析构函数抛出异常,但处理析构函数内的异常.所以没有问题.
如果更改代码以删除try ... catch
析构函数中的块,则析构函数中A
抛出的异常不会在析构函数中处理,因此最终会调用std::terminate()
.
编辑:正如Brian在他的回答中指出的那样,这条规则在C++ 11中有所改变:析构函数是隐式的noexcept
,所以你的代码应该terminate
在B
对象被销毁时调用.将析构函数标记为noexcept(false)
"修复"此内容.