繁体   English   中英

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

[英]std::move_if_noexcept calls copy-assignment even though move-assignment is noexcept; why?

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

尽管以下类中的move-assignment运算符标记为noexcept ,但在使用相关函数的返回值调用时,将调用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"
}

问题

  • 为什么在前一个代码段中没有调用move-assignment运算符

简介

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

  1. T&T&&T const&永远不会抛出异常,那么这个函数的目的是什么?
  2. move_if_noexcept如何神奇地推断出将使用返回值的上下文?

实现(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 ,就好像x根本没有获得移动构造函数一样


那么, move_if_noexcept是什么呢?

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

  • 潜在的移动构造函数可能会抛出,并且;
  • 类型是CopyConstructible
// 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;

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


判决

std::move是对rvalue-reference的无条件std::move_if_noexcept ,而std::move_if_noexcept取决于对象可以移动构造的方式 - 因此它应该只用于我们实际构建对象的地方,而不是在我们是分配给他们。

由于move_if_noexcept找不到标记为noexcept移动构造 noexcept ,因此调用了代码段中的复制赋值运算符 ,但由于它具有复制构造函数,因此该函数将生成一个适合此类型的类型,即A const&


请注意, 复制构造函数符合MoveConstructible类型,这意味着我们可以通过以下调整代码片段使move_if_noexcept返回rvalue-reference

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`
struct C {
  C ();
  C (C&&);
};

C c1;
C c2 (std::move_if_noexcept (c1)); // `C&&` => move-constructor
                                   //          ^ the only viable alternative
struct D {
  C ();
  C (C const&) noexcept;
};

C c1;
C c2 (std::move_if_noexcept (c1)); // C&& => copy-constructor
                                   //        ^ can be invoked with `T&&`

进一步阅读:

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM