是什么导致这种在 try 块展开期间抛出析构函数的奇怪行为?

What causes this weird behavior with throwing destructors during unwinding of a try-block?

当 try 块遇到异常时,堆栈将展开。如果在 try 块中创建了一个对象,则调用析构函数。如果析构函数抛出另一个异常,这个异常不会被捕获,程序终止。

所以如果你有:

struct A {
    ~A () noexcept(false) {
        std::cout << "A::~A" << std::endl;
        throw std::runtime_error("A::~A ERROR");
    }
};

然后你的 try-catch 块是这样的:

try {
    A a1;
    A a2;
} catch (...) {}

然后当 try 块结束时,a2 的析构函数抛出异常,异常被捕获,然后 a1 的析构函数抛出并终止程序。一切都按预期工作。

但是如果你引入另一个结构,它也抛出析构函数,但继承自 A 或者有一个 A 的实例作为成员,事情就会变得混乱。例如,如果您有:

struct B : A {
    ~B () noexcept(false) {
        std::cout << "B::~B" << std::endl;
        throw std::runtime_error("B::~B ERROR");
    }
};

那么如果你这样做:

try {
    B b;
    A a;
} catch (...) {}

预期的结果应该是调用 A::~A 并捕获异常,然后调用 B::~B 程序终止。但是相反,在我尝试过的所有编译器中,除了 MSVC,输出是:

A::~A

B::~B

A::~A

在抛出 std::runtime_error

的实例后调用终止
  what():  A::~A ERROR

好像捕获了两个异常,第三个异常终止了程序。

如果将 B 定义为:

,也会发生同样的情况
struct B {
    ~B () noexcept(false) {
        std::cout << "B::~B" << std::endl;
        throw std::runtime_error("B::~B ERROR");
    }
    A a;
};

我还尝试了一些其他结构的组合。

不要在 catch 块中放任何东西,因为程序永远不会去那里。

是的,我知道理想情况下,析构函数甚至不应该抛出异常。看了一篇关于抛出析构函数的文章,更多的是好奇。

我认为您观察到的行为是依赖于实现的。来自 std::terminate() 上的 c++ 参考(强调我的):

std::terminate() is called by the C++ runtime when exception handling fails for any of the following reasons:

1) an exception is thrown and not caught (it is implementation-defined whether any stack unwinding is done in this case)

在您的第一种情况下,在退出范围时:

  • A 的析构函数被调用。
  • 抛出异常std::runtime_error("A::~A ERROR")
  • 这样的异常被catch(...)捕获了。
  • 展开堆栈时也会调用 B 的析构函数。
  • 此时调用std::terminate()。但它是实现定义的是否

    a) 程序立即终止并给出你期望的输出

    b) 程序展开堆栈,因此调用基 class A 的析构函数,然后终止;这就是你所看到的。

见代码live on Coliru