Scott Meyers的Effective Modern C++第29条款里面谈到:
There are thus several scenarios in which C++11″”s move semantics do you no good:
There are thus several scenarios in which C++11″”s move semantics do you no good:
第三条的原文是:
Move not usable: The context in which the moving would take place requires a move operation that emits no exceptions, but that operation isn””t declared noexcept.
这个条款是什么含义,我理解成: 如果move构造函数里面有抛出异常,那么这个move就不可用–意思move里面throw了异常就是不能编译通过?
可是我在下面的代码也能编译通过啊:
struct S{ S(){} S(S&&)noexcept{throw 1;} }; int main() { try{ S obj; S obj2(move(obj)); }catch(...){} return 0; }
如果noexcept是个君子协定,让编译器不要生成异常处理函数,我可以理解上面的代码,catch不到exception,所以程序异常中止。
但是Scott的这个条款似乎是在说,这样的代码就应该能编译通过?
到底怎么理解呢?
解决方案:20分
根据上下文我认为是这种情况:
#include <iostream> #include <type_traits> using namespace std; struct MoveWillThrow { MoveWillThrow() {} MoveWillThrow(const MoveWillThrow&) {} MoveWillThrow(MoveWillThrow&&) noexcept(false) {} }; template <typename T> enable_if_t<is_nothrow_move_constructible<T>::value> FuncNeedNoThrowMove(T&&) { } int main(int argc, char* argv[]) { FuncNeedNoThrowMove(MoveWillThrow{}); //编译失败:MoveWillThrow不是noexcept保证的 return EXIT_SUCCESS; }
解决方案:20分
说的是有些使用环境要求操作必须保证没有异常,而有些 move 却不声明为 noexcept 的,那默认就是可以扔异常的。在这种环境中,如果调用 move 操作,并且扔了异常,就很麻烦了;所以 move 在这种情况下没什么用,宁可调没有异常的 copy,或采用其他方式处理。
举个例子:std::vector::resize 的时候,如果重新分配内存了,则需要在新的地址上重新构造元素,这时候有两种选择,使用 move 构造,或使用 copy 构造。一般的实现是,如果 move 能保证 noexcept,则使用 move,否则使用 copy,所以标准库专门有个函数叫 move_if_noexcept。在这个例子里,如果 move throw 了,结果可能是灾难性的,因为新的元素没有构造上,而旧的元素又已经 move 过了,很可能造成数据丢失;而如果用 copy,即便 throw 了,顶多也就是新的没构造上,旧的最起码还在,虽然慢点,但总比把活儿干砸了强。这就是 Scott 说的 move 用不上的意思。
举个例子:std::vector::resize 的时候,如果重新分配内存了,则需要在新的地址上重新构造元素,这时候有两种选择,使用 move 构造,或使用 copy 构造。一般的实现是,如果 move 能保证 noexcept,则使用 move,否则使用 copy,所以标准库专门有个函数叫 move_if_noexcept。在这个例子里,如果 move throw 了,结果可能是灾难性的,因为新的元素没有构造上,而旧的元素又已经 move 过了,很可能造成数据丢失;而如果用 copy,即便 throw 了,顶多也就是新的没构造上,旧的最起码还在,虽然慢点,但总比把活儿干砸了强。这就是 Scott 说的 move 用不上的意思。