第47条有一半错吗?

Is GotW #47 halfly wrong?

本文关键字:一半 47条      更新时间:2023-10-16

gotw# 47

错误的解决方案

"啊哈,"许多人——包括许多专家——说,"让我们使用uncaught_exception()来弄清楚我们是否可以抛出!"这就是问题2中的代码的来源。这是一个解决图解问题的尝试:

//  The wrong solution
//
T::~T() {
  if( !std::uncaught_exception() ) {
    // ... code that could throw ...
  } else {
    // ... code that won't throw ...
  }
}

这个想法是"我们将使用可以投掷的路径,只要它是安全的。"这种哲学在两个方面是错误的:首先,这段代码没有做到这一点;第二(也是更重要的),哲学本身是错误的。

错误的解决方案:为什么代码是不健全的

一个问题是,在某些情况下,上面的代码实际上不会像预期的那样工作。考虑:

//  Why the wrong solution is wrong
//
U::~U() {
  try {
    T t;
    // do work
  } catch( ... ) {
    // clean up
  }
}

如果U对象在异常传播期间由于栈展开而被销毁,T::~T将无法使用"可能抛出的代码"路径,即使它可以安全地执行

我认为上面的解释是完全不正确的,如果std::uncaught_exception返回true,那么总是让任何函数(包括析构函数)以另一个异常退出是不安全的。证明

如果在堆栈展开期间调用的任何函数,在异常对象初始化之后,在异常处理程序开始之前,以异常退出,则调用std::terminate。这类函数包括具有自动存储持续时间的对象的析构函数(其作用域已退出),以及调用(如果未省略)异常对象的复制构造函数来初始化按值捕获参数。

c++中的相同单词(终止符在~YYY()中调用):

#include <exception>
#include <iostream>
int main(int argc, char* argv[])
{
    struct YYY
    {
        ~YYY()
        {
            std::cout << "during stack unwinding before throwing second exception " << std::uncaught_exception() << std::endl;
            throw std::exception();
        }
    };
    struct XXX
    {
        ~XXX()
        {
            std::cout << "after first exception thrown but not catched " << std::uncaught_exception() << std::endl;
            if (std::uncaught_exception())
            {
                try
                {
                    YYY yyy;
                }
                catch (const std::exception&)
                {
                    std::cout << "in second exception catch block " << std::uncaught_exception() << std::endl;
                }
            }
        }
    };
    try
    {
        XXX xxx;
        std::cout << "before throwing first exception " << std::uncaught_exception() << std::endl;
        throw std::exception();
    }
    catch (const std::exception&)
    {
        std::cout << "in first exception catch block " << std::uncaught_exception() << std::endl;
    }
    std::cout << "after both exceptions catched " << std::uncaught_exception() << std::endl;
    return 0;
}

我的问题是,我错过了什么,Herb Sutter在某些特定情况下是正确的,还是他在这部分解释中完全错误?

这是一个关于标准文本中"在堆栈展开期间调用的任何函数"的含义的问题。

我相信这样做的目的是为了防止"任何由堆栈展开机制直接调用的函数"以异常终止,即抛出另一个(新的)异常到活动堆栈展开会话中。此要求不应用于任何由原始堆栈展开会话调用的函数在内部调用的后续(嵌套)函数。

只要新的异常是在内部抛出和捕获的,不允许逃逸到活动堆栈展开会话中,它们就是允许的。Herb的解释与标准完全一致:只要在内部拦截和抑制堆栈展开,就有可能抛出新的异常。

您的示例调用terminate()是出于不同的原因。您可能正在使用后c++ 11编译器进行编译。在c++ 11中,默认的析构函数是noexpect,这就是为什么你的YYY::~YYY()只是调用terminate(),而不管堆栈展开是否正在进行,或者任何其他外部条件(GCC甚至会警告你)。

声明为

~YYY() throw(std::exception) // or `noexcept(false)`
{
  ...

来测试代码的预期行为。不,它不调用terminate(): http://coliru.stacked-crooked.com/a/296ffb43b774409e

Herb的过时代码显然也存在同样的问题。