为什么你不能从析构函数中抛出。例

时间:2014-10-16 02:52:54

标签: c++ exception destructor

我已经读过,因为堆栈展开而抛出析构函数并不是一个好主意。我不确定我完全明白这一点。所以我尝试了以下示例

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()被称为?

4 个答案:

答案 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完成了什么是正确的事情?无论采用哪种方式,您最终都会遇到部分破坏的物体或内存泄漏。