Item 25: 对右值引用使用std::move,对universal引用则使用std::forward,这二者如何选择?

摘要:本文翻译自《effective modern C++》,由于水平有限,故无法保证翻译完全正确,欢迎指出错误。谢谢! 博客已经迁移到 "这里啦" 右值引用
本文翻译自《effective modern C++》,由于水平有限,故无法保证翻译完全正确,欢迎指出错误。谢谢! 博客已经迁移到这里啦 右值引用只能绑定那些有资格被move的对象上去。如果你有一个右值引用类型的参数,你就知道这个被绑定的对象可以被move: class Wdiget{ Widget(Widget&& rhs); // rhs肯定指向一个有资格被move的对象 ... }; 在这种情况下,你会想传这样一个对象给其他函数,来允许这些函数能利用对象的右值属性。为了达到这样的目的,需要把绑定到这些对象的参数转换成右值。就像Item 23解释的那样,std::move不仅是这么做了,它就是为了这个目的而被创造出来的: class Widget{ public: Widget(Widget&& rhs) // rhs是一个右值引用 : name(std::move(rhs.name)), p(std::move(rhs.p)) {...} ... private: std::string name; std::shared_ptr<SomeDataStructure> p; }; 在另一方面,一个universal引用可能(译注:只是可能不是一定)被绑定到一个有资格被move的对象上去。universal引用只在它由右值初始化的时候需要被转换成一个右值。Item 23解释了这就是std::forward具体做的事情: class Widget { public: template<typename T> void setName(T&& newName) // newName是一个 { name = std::forward<T>(newName); } // universal引用 ... }; 总之,因为右值引用总是被绑定到右值,所以当它们被转发给别的函数的时候,应该被无条件地转换成右值(通过std::move),而universal引用由于只是不定时地被绑定到右值,所以当转发它们时,它们应该被有条件地转换成右值(通过std::forward)。 Item 23解释了对右值引用使用std::forward能让它显示出正确的行为,但是源代码会因此变得冗长、易错、不符合习惯的,所以你应该避免对右值引用使用std::forward。对universal引用使用std::move是更加糟糕的想法,因为这样会对左值(比如,局部变量)产生非预期的修改: class Widget { public: template<typename T> void setName(T&& newName) // universal引用 { name = std::move(newName); } // 能通过编译,但是 ... // 这代码太糟糕了 private: std::string name; std::shared_ptr<SomeDataStructure> p; }; std::string getWidgetName(); // 工厂函数 Widget w; auto n = getWidgetName(); // n是局部变量 w.setName(n); // 把n move到w中去! ... // n的值现在是未知的 这里,局部变量n被传给w.setName,调用者完全可以假设这是一个对n只读的操作。但是因为stdName在内部会用了std::move,然后无条件地将他的引用参数转换成了右值,所以n的值将被move到w.name中去,最后在setNamen调用完成之后,n将成为一个未知的值。这样的行为会让调用者很沮丧,甚至会气得砸键盘! 你可能指出stdName不应该声明它的参数为universal引用。虽然这样的引用不能是const的(看Item 24,译注:加const就成右值引用了),但是steName确实不应该修改它的参数。你还可能指出如果setName使用const 左值和右值进行重载,整个问题将被避免。
阅读全文