【问题标题】:Why does the code crash?为什么代码会崩溃?
【发布时间】:2009-08-27 16:18:12
【问题描述】:

在互联网上寻找 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 的析构函数。

但是,当我运行程序时,它实际上在标有

【问题讨论】:

  • 如果下面的描述(每个人)都是正确的,那么当异常在 A 的析构函数中被抛出时,它仍然应该在调用 terminate() 之前打印 A::~A()。

标签: c++ exception


【解决方案1】:

如果你真的在编码,不仅仅是脑筋急转弯永远不要从析构函数中抛出异常。如果在堆栈展开期间抛出异常,则调用terminate()。在您的情况下,A 的析构函数在处理 B 的构造函数中抛出的异常时抛出。

编辑: 更准确地说(如 cmets 中所建议的) - 永远不要让异常逃脱析构函数。在析构函数中捕获的异常没有问题。但是,如果在堆栈展开期间程序必须处理两个异常 - 一个导致堆栈展开,另一个在展开期间逃脱析构函数,std::terminate() 去。

【讨论】:

  • 谢谢。我实际上从这些脑筋急转弯中学到了一些东西。那么,程序崩溃是因为在堆栈展开期间抛出了异常,还是因为在处理另一个异常时抛出了异常?
  • 15.2.3:“为在从 try 块到 throw 表达式的路径上构建的自动对象调用析构函数的过程称为“堆栈展开”。 [注意:如果在堆栈展开期间调用的析构函数以异常退出,则调用 std::terminate (15.5.1)。因此析构函数通常应捕获异常,而不是让它们传播出析构函数。”术语“处理异常”并不准确,可以理解为堆栈展开过程,当不允许另一个异常时,或异常处理过程(一个catch块),抛出异常是可以的。
  • 更正:如果一个异常在另一个异常正在传播(堆栈展开)时转义析构函数,则调用 terminate()。在析构函数中使用异常没有问题,只要确保在析构函数完成之前捕获它们即可。
【解决方案2】:

B::B() 抛出异常时,堆栈展开开始。 A::~A() 被调用并抛出另一个未在 A::~A() 中捕获的异常。

如果另一个异常在堆栈展开过程中留下析构函数,则调用 terminate() 并且看起来像程序崩溃。

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 我认为它在所有情况下都有很好的定义(可能是错误的)。现在诚然,这不是一个好主意。
    • @Martin C++ 标准规定,存储在容器中的对象不能有抛出的析构函数——它没有定义如果它们发生会发生什么。
    【解决方案4】:

    代码崩溃是因为B的C'tor抛出异常,然后启动“Stack Unwinding”过程:所有本地对象都被销毁,dus,A的D'tor被调用,在Stack unwinding期间抛出异常,然后调用“abort”,因为不能同时出现两个异常……

    结论:

    永远不要从 D'tor 抛出异常,因为您可能会在 Stack Unwinding 期间抛出异常。

    【讨论】:

      【解决方案5】:

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

      A::~A() 然后抛出异常。由于B::B() 的异常仍在堆栈中抛出,这会导致程序崩溃。

      【讨论】:

      • 不会崩溃:调用了 terminate()。
      【解决方案6】:

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

      【讨论】:

        猜你喜欢
        • 2018-10-28
        • 1970-01-01
        • 1970-01-01
        • 2012-12-09
        • 1970-01-01
        • 2019-01-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多