条款 25 - 针对右值引用实施 std move, 针对万能引用实施 std forward
我们在条款23了解了std::move 与 std::forward, 而条款25提供了一个简单、强大且几乎永远正确的指导方针,用于决定何时使用 std::move 以及何时使用 std::forward, 那就是: - 对右值引用,总是使用 std::move 来进行转发。 - 对万能引用,总是使用 std::forward 来进行转发。
std::move 用于右值引用
右值引用(如 Widget&&)在形参中有一个明确的特性:它只能绑定到右值,即那些可以被移动的对象。虽然形参本身(例如 rhs)是一个左值,但我们确切地知道它所引用的对象是临时的或被显式标记为可移动的。
因此,当我们要将这个形参传递给其他函数(例如,在构造函数中初始化成员)时,我们应该无条件地将其转换为右值,以触发移动操作。std::move 就是执行这种无条件转换的工具。
如下, 在移动构造函数中,形参 rhs 是一个右值引用。我们使用 std::move 来移动其成员 name 和 p 到当前对象的成员中。
1 | class Widget { |
这个指导方针也适用于按值返回的函数。当你在 return 语句中返回一个绑定到右值引用的形参对象时,应该相应地使用 std::move 来避免拷贝, 提升效率。
1 | Matrix operator+(Matrix&& lhs, const Matrix& rhs) { |
然而, 如果你的 return 语句中返回的是局部变量, 千万不要对局部变量使用 std::move, 例如这样的代码
1 | Widget makeWidget() { |
return w;
这样的语句,编译器通常可以直接在为函数返回值分配的内存中构造
局部变量w,从而完全避免任何复制或移动操作。同时, C++标准规定,在
return 语句中,局部变量会被自动视为右值。这意味着 return w;
的行为等同于 return std::move(w);
如果使用 std::move(w) 会将 w 转换为右值引用。返回一个局部对象的引用会阻止编译器执行 RVO。因此,添加 std::move 不仅没有好处,反而可能阻止一项重要的编译器优化,弄巧成拙, 导致性能下降。
而返回形参时, 由于形参不是函数内部创建的局部对象, 被视为左值,且不受RVO规则的约束。为了触发移动,你必须使用 std::move
std::forward 用于万能引用
万能引用(在模板中声明为 T&&)则不同,它既可以绑定到右值,也可以绑定到左值。我们需要在转发它时保留其原始的左/右值属性。如果原始实参是右值,我们就应该将其作为右值转发;如果原始实参是左值,我们就应该将其作为左值转发 。
因此, std::forward 正是执行这种有条件的转换的工具。它会检查模板参数 T 的推导类型,并仅在原始实参是右值的情况下,才将形参转换为右值。
如下,在一个接受万能引用的 setName 函数中,我们使用 std::forward 来将 newName 转发给成员 name 的赋值运算符, 从而确保了当 setName 接收到右值时,会触发 std::string 的移动赋值;而当接收到左值时,则触发复制赋值。
1 | class Widget { |
同理, 当你在 return 语句中返回一个绑定到万能引用形参的对象时,应该相应地使用 std::forward。这里的情况与上述的std::move一致
1 | template<typename T> |