我应该使用 std::uncaught_exceptions() 来决定是否从我的 dtor 抛出异常吗?

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)

或者这只是巴洛克风格而不是一个好主意?

注意:此类无法访问标准输出/错误流,也无法访问日志等。

Bri*_*ian 6

如果您所做的唯一一件事就是检查是否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()失败从析构函数中抛出的任何目的都将无法实现(迫使析构函数吞下错误)条件或终止程序)。这种情况很少发生。