C++ std::move_if_noexcept调用复制分配,即使move分配是noexcept;为什么?
我试图尽可能接近强异常保证,但在使用C++ std::move_if_noexcept调用复制分配,即使move分配是noexcept;为什么?,c++,c++14,C++,C++14,我试图尽可能接近强异常保证,但在使用std::move_if_noexcept时,我遇到了一些看似奇怪的行为 尽管以下类中的移动赋值运算符标记为noexcept,但当使用相关函数的返回值调用时,将调用复制赋值运算符 struct A { A () { /* ... */ } A (A const&) { /* ... */ } A& operator= (A const&) noexcept { log ("copy-assign&
std::move_if_noexcept
时,我遇到了一些看似奇怪的行为
尽管以下类中的移动赋值运算符标记为noexcept
,但当使用相关函数的返回值调用时,将调用复制赋值运算符
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";
}
};
问题
- 为什么在前面的代码段中没有调用move赋值操作符
move\u if\u noexcept
的名称当然意味着,只要此操作是noexcept
,函数就会产生一个右值引用,记住这一点,我们很快就会意识到两件事:
T&
到T&
或T const&
的简单强制转换永远不会引发异常,那么该函数的用途是什么move\u如何神奇地推断出使用返回值的上下文
move\u if\u noexcept
根本无法推断出这样的上下文(因为它不是读心器),这反过来意味着该函数必须按照一组静态规则运行
TL;博士
move\u if\u noexcept
将根据参数类型的move-构造函数的异常规范,有条件地返回右值引用,并且该引用仅用于初始化对象时(即不用于分配对象时)
这基本上意味着,只有当它能够证明它是唯一可行的替代方案,或者如果它保证不会抛出异常(通过noexcept
表示),它才会产生一个右值引用
判决书
std::move
是对右值引用的无条件强制转换,而std::move_if_noexcept
取决于对象的移动构造方式-因此,它应该只在我们实际构造对象的地方使用,而不是在分配对象时使用
template<class T>
void intended_usage () {
T first;
T second (std::move_if_noexcept (first));
}
如果noexcept找不到标记为noexcept
的移动构造函数,将调用代码段中的复制赋值运算符,但由于它有一个复制构造函数,该函数将生成一个类型,一个常量&
,该类型适用于这种情况
请注意,复制构造函数符合可移动构造函数的类型,这意味着我们可以通过对代码段进行以下调整,使
move\u if\u noexcept
返回右值引用:
struct A {
A () { /* ... */ }
A (A const&) noexcept { /* ... */ }
...
};
例子
进一步阅读:
std::move_if_noexcept
的行为似乎取决于移动构造,而不是赋值,但在问题提示中,由于特殊的函数生成规则,您根本没有移动构造函数,更不用说移动构造函数了。如果我添加一个,那么它确实会打印出“move assign”。(请参阅:)虽然我不太喜欢假设,但我认为,如果有人费心编写noexcept
move构造函数,那么他们的move赋值操作符也是noexcept
,这是半合理的假设。因此,我希望std::move_如果没有异常
在99.9%的时间里做了预期的事情。当然,我以前在行业中见过0.1%的案例,所以,这一发现值得称赞!关于提交缺陷报告的第一步:。很抱歉有这么多背对背的注释。@标记:复制构造函数符合使对象可移动可构造的条件,并且由于声明的函数未标记为noexceptis_nothrow\u move\u constructible::value==false
;我将把这一点添加到答案中,以澄清问题。
// 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;
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&&`