简体   繁体   English

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

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

I'm trying to get as close to the Strong Exception Guarantee as possible, but when playing around with std::move_if_noexcept I ran into some seemingly weird behavior. 我试图尽可能接近强异常保证 ,但是在使用std::move_if_noexcept我遇到了一些看似奇怪的行为。

Despite the fact that the move-assignment operator in the following class is marked noexcept , the copy-assignment operator is called when invoked with the return value of the function in question. 尽管以下类中的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"
}

The Question 问题

  • Why isn't the move-assignment operator called in the previous snippet? 为什么在前一个代码段中没有调用move-assignment运算符

The Introduction 简介

The name of move_if_noexcept certainly implies that the function will yield an rvalue-reference as long as this operation is noexcept , and with this in mind we soon realize two things: move_if_noexcept的名称当然意味着只要此操作是noexcept ,该函数将产生一个rvalue-reference ,并且考虑到这一点,我们很快就会意识到两件事:

  1. A simple cast from T& to T&& or T const& can never throw an exception, so what is the purpose of such function? T&T&&T const&永远不会抛出异常,那么这个函数的目的是什么?
  2. How can move_if_noexcept magically deduce the context in which the returned value will be used? move_if_noexcept如何神奇地推断出将使用返回值的上下文?

The answer to realization (2) is equally scary as natural; 实现(2)的答案与自然同样可怕; move_if_noexcept simply can't deduce such context (since it's not a mind-reader), and this in turn means that the function must play by some static set of rules. move_if_noexcept根本无法推断出这样的上下文(因为它不是思维读者),这反过来意味着该函数必须通过一些静态规则集来发挥作用。


The TL;DR TL; DR

move_if_noexcept will, no matter the context in which it is called, conditionally return an rvalue-reference depending on the exception specification of the argument type's move- constructor , and it was only meant to be used when initializing objects (ie. not when assigning to them). move_if_noexcept ,无论调用它的上下文,都会有条件地返回一个rvalue-reference,具体取决于参数类型的move- 构造函数的异常规范,并且它只是在初始化对象时使用(即,不是在分配时)他们)。

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

A better name could have been move_if_move_ctor_is_noexcept_or_the_only_option ; 一个更好的名字可能是move_if_move_ctor_is_noexcept_or_the_only_option ; though a bit tedious to type, at least it would have expressed the intended usage. 虽然输入有点乏味,但至少它会表达预期用途。


The Birth Of move_if_noexcept move_if_noexcept的诞生

Reading the proposal ( n3050 ) that gave birth to std::move_if_noexcept , we find the following paragraph (emphasize mine): 阅读产生std::move_if_noexcept的提案( n3050 ),我们找到以下段落(强调我的):

We propose that instead of using std::move(x) in those cases, thus granting permission for the compiler to use any available move constructor , maintainers of these particular operations should use std::move_if_noexcept(x) , which grants permission move unless it could throw and the type is copyable. 我们建议在这些情况下不使用std::move(x) ,因此授予编译器使用任何可用移动构造函数的权限,这些特定操作的维护者应该使用std::move_if_noexcept(x) ,它授予权限移动,除非它可以抛出,类型是可复制的。

Unless x is a move-only type, or is known to have a nonthrowing move constructor , the operation would fall back to copying x , just as though x had never acquired a move constructor at all. 除非x是一个只移动类型,或者已知有一个非平移移动构造函数否则操作将回退到复制x ,就好像x根本没有获得移动构造函数一样


So, what is it that move_if_noexcept does? 那么, move_if_noexcept是什么呢?

std::move_if_noexcept will conditionally cast the passed lvalue-reference to an rvalue-reference , unless; std::move_if_noexcept将有条件地将传递的左值引用转换为rvalue-reference ,除非;

// 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;

This basically means that it will only yield an rvalue-reference if it can prove that it is the only viable alternative, or if it is guaranteed not to throw an exception (expressed through noexcept ). 这基本上意味着如果它能证明它是唯一可行的替代方案,或者如果保证不抛出异常(通过noexcept表示),它将只产生一个rvalue-reference


The Verdict 判决

std::move is an unconditional cast to an rvalue-reference , whereas std::move_if_noexcept depends on the ways an object can be move-constructed - therefore it should only be used in places where we are actuallying constructing objects, not when we are assigning to them. std::move是对rvalue-reference的无条件std::move_if_noexcept ,而std::move_if_noexcept取决于对象可以移动构造的方式 - 因此它应该只用于我们实际构建对象的地方,而不是在我们是分配给他们。

The copy-assignment operator in your snippet is invoked since move_if_noexcept can't find a move-constructor marked noexcept , but since it has a copy-constructor the function will yield a type, A const& , that is suitable for such. 由于move_if_noexcept找不到标记为noexcept移动构造 noexcept ,因此调用了代码段中的复制赋值运算符 ,但由于它具有复制构造函数,因此该函数将生成一个适合此类型的类型,即A const&


Please note that a copy-constructor qualifies as the type being MoveConstructible , this means that we can make move_if_noexcept return an rvalue-reference through the following adjustment of your snippet: 请注意, 复制构造函数符合MoveConstructible类型,这意味着我们可以通过以下调整代码片段使move_if_noexcept返回rvalue-reference

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

  ...
};

Examples 例子

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&&`

Further reading: 进一步阅读:

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

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