2009-08-26 206 views
0

展望互联网的C++脑筋急转弯,我发现这个例子:为什么代码崩溃?

#include <iostream> 

using namespace std; 

class A { 
public: 
    A() 
    { 
     cout << "A::A()" << endl; 
    } 

    ~A() 
    { 
     cout << "A::~A()" << endl; 
     throw "A::exception"; 
    } 
}; 

class B { 
public: 
    B() 
    { 
     cout << "B::B()" << endl; 
     throw "B::exception"; // <- crashes here 
    } 

    ~B() 
    { 
     cout << "B::~B()"; 
    } 
}; 

int main(int, char**) { 
    try 
    { 
     cout << "Entering try...catch block" << endl; 

     A objectA; 
     B objectB; 

     cout << "Exiting try...catch block" << endl; 
    } 
    catch (const char* ex) 
    { 
     cout << ex << endl; 
    } 

    return 0; 
} 

这是我认为该计划会做:

  1. A :: A()时,输出到屏幕objectA的构造函数被调用。对象A构造成功。
  2. 当调用objectB的构造函数时,B :: B()将被输出到屏幕。
  3. B的构造函数然后抛出异常。对象B未被成功构建。
  4. objectB的析构函数未被调用,因为构造函数从未成功完成。
  5. 当try块退出时,objectA的析构函数将被调用,因为对象超出了范围。

但是,当我运行该程序时,它实际上在标记为<的行上崩溃。任何人都可以解释那个时候究竟发生了什么?

+0

如果下面的描述(由所有人)是正确的,那么它应该仍然在A的析构函数中发生异常时调用terminate()之前先打印A ::〜A()。 – 2009-08-26 15:29:20

回答

11

如果你真的是编码,不仅仅是令人着迷的永远不会从析构函数中抛出异常。如果在堆栈展开期间引发异常,则调用terminate()。在你的情况下,A的析构函数在处理B的构造函数中抛出的异常时抛出。

编辑: 更确切地说(如评论中所建议的) - 永远不要让异常转义析构函数。在析构函数内捕获的异常没有任何问题。但是如果在堆栈展开程序中必须处理两个异常 - 导致堆栈展开的那个异常以及在展开期间逃脱析构函数的异常,那么std::terminate()就会发生。

+0

谢谢。我实际上从这些蝙蝠侠中学到了一些东西。 因此,程序崩溃是因为在堆栈展开期间抛出异常,还是因为在处理另一个异常时引发异常? – Andy 2009-08-26 10:10:13

+0

15.2.3:“调用从try块到throw-expression的路径上构造的自动对象的析构函数的过程称为”堆栈展开“。[注意:如果在堆栈展开期间调用的析构函数退出时出现异常,std :: terminate被调用(15.5.1)。因此析构函数通常应该捕获异常,而不是让它们从析构函数中传播出去。“术语“处理异常”并不精确,通过它可以了解堆栈展开过程,何时不允许另一个异常,或异常处理过程(catch块),其中抛出异常是OK。 – 2009-08-26 10:56:31

+0

更正:如果异常在传播另一个异常(堆栈展开)时调用析构函数,则调用terminate()。在析构函数中使用异常没有问题,只要确保在析构函数完成之前捕获它们即可。 – 2009-08-26 15:22:24

4

在C++中的黄金法则 - 析构函数绝不应该抛出异常。忽略这一规则将导致在各种情况下未定义的行为。

+0

我认为它在所有情况下都很好定义(可能是错误的)。诚然,它不是一个好主意。 – 2009-08-26 15:23:54

+0

@Martin C++标准指定存储在容器中的对象不得有析构函数抛出 - 它没有定义如果他们这样做会发生什么。 – 2009-08-26 15:28:52

2

A::~A()在try块退出时调用,正是因为该对象超出了范围。这就是为什么RAII工作原理 - 它取决于被调用的析构函数,而不管范围退出的原因。

A::~A()然后引发异常。由于B::B()的异常仍然被抛出堆栈,这使程序崩溃。

+0

不会崩溃:调用terminate()。 – 2009-08-26 15:30:59

2

你应该从不在析构函数中抛出一个异常。看看this question为什么。

6

B::B()引发异常堆栈开始展开。 A::~A()被调用,并抛出另一个例外,A::~A()内没有捕捉到。

如果另一个异常在堆栈展开正在进行时遗留析构函数,则会调用terminate()并且看起来像程序崩溃。

3

代码崩溃是因为B的C'tor抛出一个异常,然后启动“堆栈展开”过程:所有本地对象被破坏,dus,A的D'tor被调用,并在堆栈展开期间引发异常,然后“中止”被称为,因为不能有两个例外在同一时间...

结论:

切勿从D'TOR抛出一个异常,因为你可能栈展开过程中把它扔。

相关问题