是什么导致这种在 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
的析构函数,然后终止;这就是你所看到的。
当 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
的析构函数,然后终止;这就是你所看到的。