右值引用仅会绑定到那些可移动的对象上(临时对象)
class Widget {
public:
Widget(Widget && rhs) : name(rhs.name), p(std::move(rhs.p)) {}; // 移动构造函数
private:
string name;
shared_ptr<int> p;
};
万能引用只有在使用右值初始化时候才会强制转换成右值类型,正好是std::forward
作用
class Widget {
public:
template<typename T>
void setName(T &&newName) {
name = std::forward<T>(newName); // newName是个万能引用
}
//...
};
需要避免针对右值引用实施std::forward
,也要避免针对万能引用使用std::move
:
class Widget {
public:
template<typename T>
void setName(T&& newName){
name = std::move(newName); //万能引用,可以编译,但是糟糕透顶
}
private:
std::string name;
std::share_ptr<SomeDataStructure> p;
};
std::string getWidgetName(); // 工厂函数
Widget w;
auto n = getWidgetName(); // n为局部变量
w.setName(n); // 將n移入w!,n的值未知
局部变量n被传递给w.setName
,而调用者会合情合理地假定这是一个对n的只读操作。但由于setName
函数内部使用了std::move
把它的引用形参无条件的强制转换到右值,n
的值就会被移入w.name
。这么一来调用完毕setName
后,n
將变成一个不确定的值
你可能会说setName
不应该將形参声明为一个万能引用,因为万能引用不能带有const
饰词,你可能会为setName
写出常量左值和右值的不同的重载:
class Widget {
public:
void setName(const std::string &newName){
name = newName;
}
void setName(const std::string &&newName){
name = std::move(newName);
}
private:
std::string name;
std::shared_ptr<int> p;
};
有两个缺点:
- 需要编写和维护更多的代码
- 效率打折扣,会执行一次
std::string
构造函数(以创建临时对象),一次std::string
移动赋值运算符(以移动newName
到w.name
),还有一次string
析构函数(以销毁临时变量)
在按值返回的函数中,如果返回的是绑定到一个右值引用或者万能引用的对象,则当你返回该引用时候,应该对其实施std::move
或则std::forward
:
Matrix operator+(Matrix &&lhs, const Matrix &rhs) {
lhs += rhs;
return std::move(lhs); // 將lhs移入返回值
}
Matrix operator+(Matrix &&lhs, const Matrix &rhs) {
lhs += rhs;
return lhs; // 將lhs复制入返回值
}
lhs
为左值会强迫编译器將其复制入返回值存储位置,假定Matrix
类型支持移动构造,这將币复制构造效率更高,从而在返回语句中使用std::move
会产生更高效的代码
某局部对象可能适用于返回值优化,则请勿针对其实施std::move
或std::forward