![](/img/trans.png)
[英]Move-assignment slower than copy-assignment — bug, feature, or unspecified?
[英]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_if_noexcept
的名称当然意味着只要此操作是noexcept
,该函数将产生一个rvalue-reference ,并且考虑到这一点,我们很快就会意识到两件事:
T&
到T&&
或T const&
永远不会抛出异常,那么这个函数的目的是什么? move_if_noexcept
如何神奇地推断出将使用返回值的上下文? 实现(2)的答案与自然同样可怕; move_if_noexcept
根本无法推断出这样的上下文(因为它不是思维读者),这反过来意味着该函数必须通过一些静态规则集来发挥作用。
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 ,除非;
// 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.