[英]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 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 ,并且考虑到这一点,我们很快就会意识到两件事:
T&
to T&&
or T const&
can never throw an exception, so what is the purpose of such function? T&
到T&&
或T const&
永远不会抛出异常,那么这个函数的目的是什么? 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
根本无法推断出这样的上下文(因为它不是思维读者),这反过来意味着该函数必须通过一些静态规则集来发挥作用。
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. 虽然输入有点乏味,但至少它会表达预期用途。
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 usestd::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 copyingx
, just as thoughx
had never acquired a move constructor at all.除非
x
是一个只移动类型,或者已知有一个非平移移动构造函数 , 否则操作将回退到复制x
,就好像x
根本没有获得移动构造函数一样 。
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 。
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 { /* ... */ }
...
};
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.