当形参被声明为右值引用时,意味着传入的实参需要是右值引用,并且该参数是可移动的。既然目的如此明确,那么使用std::move
是正确的选择:
1
2
3
4
5
6
7
8
9
10
11class Widget {
public:
Widget(Widget&& rhs) // rhs is rvalue reference
: name(std::move(rhs.name)),
p(std::move(rhs.p))
{ … }
…
private:
std::string name;
std::shared_ptr<SomeDataStructure> p;
};std::forward
是正确的选择:
1
2
3
4
5
6
7class Widget {
public:
template<typename T>
void setName(T&& newName) // newName is
{ name = std::forward<T>(newName); } // universal reference
…
};
如果将std::move
应用于通用引用会怎样?
如下代码所示:
1 |
|
其输出为:
The contents of str before move : hello,world The contents of str after move :
作为widget
的使用者,其本意是将str
的内容拷贝一份给widget
,但是由于使用了std::move
进行无条件转换。最终str
指向的内容被移动到了widget
的私有成员name
中。
但作为使用者还以为str
中依然是原来的内容,如果继续操作str
便会遇到未定义的错误!
对返回值使用std::move
或std::forward
好心办坏事
当同时满足以下两个条件时,c++ 会应用返回值优化策略(return value optimization,RVO):
- 局部变量的类型和函数的返回类型一致
- 当前局部变量就是被返回的变量
那么编译器会尝试使用移动语义,以提高返回变量的效率。
假设在满足 RVO
的前提下,用户主动使用std::move
转换返回的变量会怎么样?
1 | Widget makeWidget() |
其实这是会起反作用的,因为使用std::move
转换后,返回的类型便是Widget
的右值引用了,这反而影响了编译器的判断(不满足条件
2)而最终使用拷贝的方式返回。
相当于用户手动的实现了移动操作,而绕过了编译器的自主优化。
正确的使用场景
那么哪种情况下比较适合主动使用std::move
或std::forward
呢?
也就是返回的变量不满足上面两个条件时,可以主动使用移动语义:
1 | Matrix // by-value return |
上面这个+
重载函数是以值的方式返回,参数lhs
是一个通用引用。
这种情况下使用std::move
转换一下,编译器便会尝试使用移动语义来提高效率:
- 如果
Matrix
类支持移动操作,那么就会调用移动操作。 - 如果
Matrix
类不支持移动操作,那么就会使用拷贝操作
1 | template<typename T> |
当参数frac
是左值时,那么就使用拷贝操作。
当参数frac
是右值时,则将其转换为右值引用,并尝试使用移动操作。