std :: move_if_noexcept调用copy-assignment,即使move-assignment是noexcept;为什么?

时间:2015-02-19 04:12:40

标签: c++ c++14

我正试图尽可能接近强烈异常保证,但在玩std::move_if_noexcept时,我遇到了一些看似奇怪的行为。

尽管以下类中的 move-assignment运算符标记为noexcept,但在使用return返回时调用 copy-assignment运算符有问题的函数的价值。

struct A {
  A ()         { /* ... */ }
  A (A const&) { /* ... */ }

  A& operator= (A const&) noexcept { log ("copy-assign"); return *this; }
  A& operator= (A&&)      noexcept { log ("move-assign"); return *this; }

  static void log (char const * msg) {
    std::cerr << msg << "\n";
  }
};
int main () {
  A x, y;

  x = std::move_if_noexcept (y); // prints "copy-assign"
}

问题

  • 为什么上一个代码段中没有调用移动分配运算符

1 个答案:

答案 0 :(得分:15)

简介

move_if_noexcept的名称肯定意味着只要此操作为noexcept,该函数将产生 rvalue-reference ,并且考虑到这一点,我们很快意识到两个事情:

  1. T&T&&T const&的简单转换永远不会抛出异常,那么此类函数的目的是什么?
  2. move_if_noexcept如何能够神奇地推断出使用返回值的上下文?
  3. 实现(2)的答案与自然同样可怕; move_if_noexcept根本无法推断出这样的上下文(因为它不是读者),而这反过来意味着该函数必须通过一些静态规则集来发挥。


    TL; DR

    move_if_noexcept,无论调用它的上下文,都会有条件地返回 rvalue-reference ,具体取决于参数类型 move- 的异常规范构造函数 ,它只是在初始化对象时使用(即,不是在分配对象时)。

    template<class T>
    void intended_usage () {
      T first;
      T second (std::move_if_noexcept (first));
    }
    

    一个更好的名字可能是move_if_move_ctor_is_noexcept_or_the_only_option;虽然输入有点乏味,但至少它会表达预期用途。


    move_if_noexcept

    的诞生

    阅读产生std::move_if_noexcept的提案(n3050),我们找到以下段落(强调我的):

      

    我们建议不要在这些情况下使用std::move(x),从而授予编译器使用任何可用的移动构造函数的权限,这些特定操作的维护者应使用std::move_if_noexcept(x) ,授予权限移动,除非它可以抛出,类型是可复制的。

         

    除非x是仅移动类型,或者已知有非投放移动构造函数,否则操作将回退到复制x,就像{ {1}}从未获得过移动构造函数


    那么,x的作用是什么?

    move_if_noexcept会有条件地将传递的左值 - 引用转换为 rvalue-reference ,除非;

    • 潜在的 move-constructor 可能会抛出,而且<; li>
    • 类型为CopyConstructible
    std::move_if_noexcept

    这基本上意味着它只会产生 rvalue-reference ,如果它能证明它是唯一可行的替代方案,或者保证不会抛出异常(通过{{1表示) }})。


    判决

    // Standard Draft n4140 : [utility]p2 template<class T> constexpr conditional_t< !is_nothrow_move_constructible::value && is_copy_constructible<T>::value, const T&, T&& > move_if_noexcept (T& x) noexcept; 是对 rvalue-reference 的无条件转换,而noexcept取决于对象可以移动构造的方式 - 因此,它只应该用于我们实际构建对象的地方,而不是在我们分配给它们时。

    由于std::move无法找到标记为std::move_if_noexcept移动构造函数,因此会调用代码段中的复制分配运算符,但是它有一个 copy-constructor ,该函数将产生一个适合的类型move_if_noexcept


    请注意, copy-constructor 符合 MoveConstructible 的类型,这意味着我们可以让noexcept返回 rvalue-reference 通过以下调整您的代码段:

    A const&

    实施例

    move_if_noexcept
    struct A {
      A ()                  { /* ... */ }
      A (A const&) noexcept { /* ... */ }
    
      ...
    };
    
    struct A {
      A ();
      A (A const&);
    };
    
    A a1;
    A a2 (std::move_if_noexcept (a1)); // `A const&` => copy-constructor
    
    struct B {
      B ();
      B (B const&);
      B (B&&) noexcept;
    };
    
    B b1;
    B b2 (std::move_if_noexcept (b1)); // `B&&` => move-constructor
                                       //          ^ it's `noexcept`
    

    进一步阅读: