【问题标题】:copy elision of return values and noexcept复制省略返回值和 noexcept
【发布时间】:2018-10-02 02:19:51
【问题描述】:

我有一个这样的函数模板:

template <typename T>
constexpr auto myfunc() noexcept
{
    return T{};
}

由于复制省略,这个函数模板是否保证为 noexcept?如果在构造函数内部抛出异常,是发生在函数内部还是函数外部?

【问题讨论】:

    标签: c++ exception optimization noexcept


    【解决方案1】:

    复制省略所做的只是消除实际的复制或移动。一切都“好像”发生,而没有发生复制省略(当然,除了复制本身)。

    构造发生在函数内部。复制省略不会改变这一点。它所做的只是消除实际复制/移动的发生(我是在重复自己吗?),因为函数的返回值被推回其调用者。

    因此,如果该类的 default 构造函数抛出异常,noexcept 会从高轨道上摧毁整个事物。

    如果复制/移动构造函数抛出异常,由于复制/移动没有发生,一切都会继续进行。

    使用 gcc 7.3.1,使用 -std=c++17 编译:

    template <typename T>
    constexpr auto myfunc() noexcept
    {
        return T{};
    }
    
    class xx {
    public:
    
        xx() { throw "Foo"; }
    };
    
    int main()
    {
        try {
            myfunc<xx>();
        } catch (...) {
        }
    }
    

    结果:

    terminate called after throwing an instance of 'char const*'
    

    现在,让我们混合起来,在复制和移动构造函数中都抛出异常:

    class xx {
    public:
    
        xx() { }
    
        xx(xx &&) { throw "Foo"; }
    
        xx(const xx &) { throw "Baz"; }
    };
    

    这将毫无例外地运行。

    【讨论】:

      【解决方案2】:

      返回值的初始化发生在被调用者(包含return语句的函数)的上下文中。也就是说,如果您想保留处理由T 的默认构造函数抛出的异常的可能性,您应该noexcept 声明myfunc

      我了解混淆的根源:根据 C++17 及更高版本中的值类别分类法,prvalue 是构造对象的方法,而不是对象本身。考虑以下代码:

      T foo() {
          return {};
      }
      T t = foo();
      

      在 C++14 中,return 语句和 t 的初始化是两个独立的步骤,尽管省略作为优化是允许的。在第一步中,返回对象(a.k.a.foo()”)从{} 复制初始化。在第二步中,t 从该返回对象复制初始化。显然,第一步发生在被调用者上下文中,第二步发生在调用者上下文中。

      所以在 C++17 中,您可能会认为发生了类似的两步过程,只是使用了修改后的纯右值概念:即,由于 foo() 是纯右值,您可能会认为 return 语句只是创建一个配方(在概念上可以表示为[](void* p) { new (p) T{}; }),并且所述配方是在被调用者上下文中创建的,而该配方的执行以创建t 将发生在调用者上下文中。如果是这种情况,那么对 T 的默认构造函数的实际调用将发生在调用者的上下文中,因此它抛出的任何异常都不会遇到被调用者的外括号。

      但是,该标准有明确的语言否认这种解释:

      return 语句通过操作数的复制初始化 [...] 来初始化(显式或隐式)函数调用的泛左值结果或纯右值结果对象。

      t的初始化是由return语句本身完成的。这意味着t 在实际离开被调用者的最外层块之前已完全初始化。例如,如果被调用者中有任何局部变量需要销毁,这实际上发生在 t 已经初始化之后(因此这种行为可能与 C++14 的行为不同)。很明显,这些局部变量的销毁发生在被调用者上下文中(因此,如果由此引发异常,则搜索处理程序将遇到foo 的最外层块),同样清楚的是t 出现在被调用者上下文中。

      【讨论】:

        【解决方案3】:

        这样做:

        template <typename T> constexpr 
        auto myfunc() noexcept(std::is_nothrow_default_constructible_v<T>)
        {
            return T{};
        }
        

        【讨论】:

          猜你喜欢
          • 2021-10-29
          • 2015-07-14
          相关资源
          最近更新 更多