右值引用
右值
右值是指表达式结束时就不再存在的临时对象。右值由两个概念构成
- 将亡值:如将要被移动的对象,T&&函数返回值,move返回值
- 纯右值:如非引用返回的临时变量,表达式产生的临时变量,原始字面量和lamba表达式
右值引用
右值引用就是对一个右值进行引用的类型。标记为T&&,因为右值不具名,所以我们只能通过引用的方式找到它。通过右值引用的声明,该右值又”重获新生”,其生命周期与右值引用类型变量的生命周期一样。
无论声明左值引用还是右值引用都必须立即初始化。
右值引用的作用就是延长临时变量的寿命以供我们使用而省的我们复制它创建一个左值变量
例如:
A Get()
{
return A();
}
A b = Get();
A&& a = Get();//(如果关掉编译器的优化)比上面少一个一次拷贝构造和一次析构,原因在于右值引用绑定了右值,让临时右值的生命周期延长了。
右值引用和左值引用类型
可以根据它们来重载函数
普通的左值引用不能接受右值。右值引用也不能接受左值
比如:
void fun(A&& a){}//只能接收右值的参数
void fun(A& a){}//只能接收左值的参数
只有当发生自动类型推断时(如函数模版的类型自动推倒,或auto关键字),&&才是一个未定的引用类型。它是左值还是右值引用取决与它的初始化。
template<typename T>
void f(T&& param);//这里T类型需要推导,所以是未定的引用类型,左值右值都可以
编译器会将已命名的右值引用视为左值,将未命名的右值引用视为右值。
auto&&或函数参数类型自动推导的T&&是个未定的引用类型,它可能是左值引用或右值引用类型,取决于他的初始化类型。
std::move
std::move方法来将左值转换为右值,move实际上并不能移动任何东西,它唯一的功能就是将一个左值强制转换为一个右值引用。如果一个类没有move语义(没定义移动构造函数),那么使用std::move也没有意义。
从实现上讲:等同与一个类型转换:static_cast< T&&>(lvalue);
move相当于一种强制类型转换
当一个对象内部有较大的堆内存,很有必要写move语义的拷贝构造函数(移动构造函数)和赋值函数,避免无谓的深拷贝,c++所有容器都实现了move语义(移动构造函数)。如果没有实现移动构造函数,那么用std::move将左值转变为右值引用将没有意义,如Moveable c(std::move(a))所说的一样,依然会调用拷贝构造函数。
class Moveable
{
public:
Moveable(const Moveable& m);//参数是左值引用
Moveable(Moveable&& m);//移动构造函数,参数是右值引用,这里不能为const,因为要修改m里的内容
{
ptr_ = m.ptr_;
m.ptr_ = NULL;
}
private:
int *ptr_;
};
A Get()
{
A a;
return a;
}
Moveable a("obj_a");
Moveable c = std::move(a);//将会调用移动构造函数,因为move后是一个右值类型
//但如果Moveable没有定义移动构造函数,a的资源并不会转移给c,而是调用了拷贝构造函数
//为什么可以调用拷贝构造函数,因为const Moveable&(常量左值引用是一个万能引用类
//型,可以接受右值,而**move返回值就是一个右值**),普通的左值引用不能接受右值。右值
//引用也不能接受左值
A tmp = Get();//对tmp的构造将调用移动构造函数
所以对应一直基本类型,如int和char[10]数组,如果使用move,仍然会发生拷贝(因为他们没有对应的移动构造函数)。
forward和完美转发
所谓完美转发,是指在函数模版中,完全依照模版的参数的类型(左值,还是右值),将参数传递给函数模板中调用的另外一个函数。c++11提供的函数std::forward就是为转发而生的,它会按照参数本来的类型转发
#include <iostream>
using namespace std;
void Print(int& t)
{
cout<<"lvalue"<<endl;
}
void Print(int&& t)
{
cout<<"rvalue"<<endl;
}
template <typename T>
void TestForward(T&& v)
{
Print(v);
Print(std::forward<T>(v));
Print(std::move(v));
}
int main()
{
TestForward(1);
int x = 1;
TestForward(x);
TestForward(std::forward<int>(x));
}
运行结果为:
qjw@ubuntuTeamLee:~/test/forward$ ./a.out
lvalue
rvalue
rvalue
lvalue
lvalue
rvalue
lvalue
rvalue
rvalue