ein*_*ica 4 c++ destructor idioms stack-unwinding exception-safety
我有一个类,其 ctor 进行驱动程序调用,其 dtor 进行匹配的终止/释放驱动程序调用。这些调用可能会失败。问题自然出在 dtor 身上。
我自然知道避免 dtor 中出现异常的常识,因为如果您在堆栈展开期间抛出异常,您会得到std::terminate. 但是 - 如果可以的话,我宁愿不只是“吞下”此类错误并且不报告它们。那么,编写这样的代码是否合法/惯用:
~MyClass() noexcept(false) {
auto result = something_which_may_fail_but_wont_throw();
if (std::uncaught_exceptions() == 0) {
throw some_exception(result);
}
}
Run Code Online (Sandbox Code Playgroud)
或者这只是巴洛克风格而不是一个好主意?
注意:此类无法访问标准输出/错误流,也无法访问日志等。
如果您所做的唯一一件事就是检查是否uncaught_exceptions()为零,那么您可能会错过一些可以安全传播异常的情况。例如,考虑
struct X {
~X() noexcept(false) {
if (std::uncaught_exceptions() == 0) throw FooException{};
}
};
struct Y {
~Y() {
try {
X x;
} catch (const FooException&) {
// handle exception
}
}
};
int main() {
try {
Y y;
throw BarException{};
} catch (const BarException&) {
// handle
}
}
Run Code Online (Sandbox Code Playgroud)
在这里,y将在堆栈展开期间被销毁。在 的析构函数期间Y,飞行中存在一个未捕获的异常。析构函数创建一个X对象,该对象的析构函数随后必须决定是否抛出FooException. 这样做是安全的,因为在FooException它到达std::terminate将被调用的点之前将有机会捕获它。但X::~X确定有未捕获的异常正在运行,因此决定不抛出该异常。
这在技术上没有任何问题,但可能会令人困惑,因为 try-catch 块的行为取决于调用的Y::~Y上下文。Y::~Y理想情况下,X::~X在这种情况下仍应抛出异常。
N4152解释正确的使用方法std::uncaught_exceptions:
想要知道是否正在运行其析构函数来展开该对象的类型可以
uncaught_exceptions在其构造函数中查询并存储结果,然后uncaught_exceptions在其析构函数中再次查询;如果结果不同,则由于晚于对象构造引发的新异常,该析构函数将作为堆栈展开的一部分被调用。
在上面的例子中,X::X()需要在构造过程中存储 的值std::uncaught_exceptions(),该值将为 1。然后,析构函数将看到该值仍然是 1,这意味着让异常从析构函数中逃逸是安全的。
这种技术应该只在你确实需要从析构函数中抛出的情况下使用,并且你可以接受这样一个事实:如果检查std::uncaught_exceptions()失败,从析构函数中抛出的任何目的都将无法实现(迫使析构函数吞下错误)条件或终止程序)。这种情况很少发生。