当形参被声明为右值引用时,意味着传入的实参需要是右值引用,并且该参数是可移动的。既然目的如此明确,那么使用std::move是正确的选择:
1 | class Widget { |
但如果形参被声明为通用引用时,则意味着实参有可能是右值引用,该参数有可能可被移动。那么对应的使用std::forward是正确的选择:
1 | class Widget { |
如果将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是右值时,则将其转换为右值引用,并尝试使用移动操作。