我正试图尽可能接近强烈异常保证,但在玩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"
}
答案 0 :(得分:15)
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
,就像{ {1}}从未获得过移动构造函数。
x
的作用是什么? move_if_noexcept
会有条件地将传递的左值 - 引用转换为 rvalue-reference ,除非;
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`