我已经读过,因为堆栈展开而抛出析构函数并不是一个好主意。我不确定我完全明白这一点。所以我尝试了以下示例
struct foo
{
~foo()
{
throw 1;
}
};
struct bar
{
~bar()
{
throw 2;
}
};
int main()
{
try
{
foo a;
bar b;
throw 3;
}catch(int a)
{
std::cout << a;
}
}
现在我期待a将是1,因为前3被抛出然后调用b的析构函数抛出2然后调用a的析构函数抛出1.显然这不是这种情况,这可能解释了为什么它的原因从析构函数中抛出并不是一个好主意。我的问题是为什么称为b的析构函数的abort()被称为?
答案 0 :(得分:6)
在 stack-unwinding 期间抛出异常会导致调用std::terminate
,其默认操作是调用std::abort
。
CERT在他们的ERR33-CPP. Destructors must not throw exceptions文档中有一个很好的解释(强调我的):
在堆栈展开期间很可能会调用析构函数 因抛出异常而导致如果是析构函数本身 抛出异常,因为异常而被调用 抛出,然后调用函数std :: terminate() 调用std :: abort()的默认效果。这可以提供 拒绝服务攻击的机会。因此,析构函数必须 满足无投保命,也就是说,他们不得抛弃 例如,如果他们自己被称为a的结果 抛出异常。
草案C ++标准部分15.2
构造函数和析构函数中包含了这一点,其中包含:
为构造的自动对象调用析构函数的过程 在从try块到throw-expression的路径上称为“堆栈” 展开。“如果在堆栈展开期间调用的析构函数退出 一个例外,调用std :: terminate(15.5.1)。 [注意:所以 析构函数通常应该捕获异常而不是让它们 传播出析构函数。 - 后注]
请注意,在C ++ 11中,只要没有调用的函数允许异常,就会隐式指定析构函数noexcept(true)
。因此,在这种情况下,从析构函数中抛出无论如何都会调用std::terminate
。
来自12.4
Destructors 部分:
没有。的析构函数的声明 异常规范被隐含地认为具有相同的规范 异常规范作为隐式声明(15.4)。
和15.4
说:
隐含声明的特殊成员函数(第12条)应具有 异常规范。如果f是隐式声明的默认值 构造函数,复制构造函数,移动构造函数,析构函数,副本 赋值运算符,或移动赋值运算符,其隐式 exception-specification指定type-id T当且仅当T为时 直接调用的函数的异常规范允许 通过f的隐含定义;如果有的话,f应允许所有例外 函数它直接调用允许所有异常,并且f 应允许 如果它直接调用的每个函数都不允许没有例外 异常。强>
理论上你可以使用std::uncaught_exception来检测析构函数中的 stack-unwinding ,但是在GotW #47中,Herb Sutter解释了为什么这种技术看起来并不像看起来那么有用。
虽然Herb最近在N4152: uncaught _exceptions
中提出了修正案答案 1 :(得分:3)
每当您在异常处理过程中抛出异常时,您都会遇到一个特殊的异常,无法捕获,这会导致中止。
您可以使用std::uncaught_exception
来检测异常处理是否已在进行中,并避免抛出这种情况。
答案 2 :(得分:0)
在某些情况下,可以添加这样的异常规范并安全地处理它们。请注意,只有在没有抛出异常时才会删除该对象。
#include<iostream>
using namespace std;
struct A{
bool a;
A():a(1){}
~A()throw(int){
if(a)a=0,throw 0;
}
};
int main(){
A*a=new A();
try{
delete a;
}catch(int&){
delete a;
cout<<"here"<<endl;
}
return 0;
}
答案 3 :(得分:0)
其他人已从标准中回答,但我认为另一个例子最能说明概念问题:
struct foo {
char *buf;
foo() : buf(new char[100]) {}
~foo() {
if(buf[0] == 'a')
throw 1;
delete[] buf;
}
};
int main() {
foo *f = new f;
try {
delete f;
} catch(int a) {
// Now what?
}
}
当然,这有点简单,但显示了问题。 delete f
完成了什么是正确的事情?无论采用哪种方式,您最终都会遇到部分破坏的物体或内存泄漏。