要了解右值引用是什么,先来看一看什么是右值?
一般认为可以取地址的称为左值,如果表达式的运行结果是一个临时变量或者对象则认为是右值,在c++11中对右值进行了严格的区分:分为了纯右值和将亡值
纯右值:如 1,2,3,a + b
将亡值:如表达式的中间结果、函数按照值的方式进行返回(临时对象)
int g_a = 10;//函数返回值结果为引用
int& Func_A() {
return g_a;
}
int main() {
int a = 10;
int b = 10;
a = b;
b = a;
//a和b 都为左值,但是a和b都可以放在 = 号的右边
//所以左值可以放在=号左右,不能仅仅根据在=号左右的位置来区分左值与右值
const int c = 30;
//编译失败,c为const常量,只读不可修改
//c = a;
//cout<< &c <<endl;
//因为可以对c取地址,所以严格来说c不是左值
//b + 1 = 20;
//编译失败因为b+1的结果是临时变量,没有具体名称,不能取地址,所以为右值
Func_A() = 100;
}
右值引用和普通引用的区别:
1.普通引用类型只能引用左值,不能引用右值
2.const引用既可以引用右值也可以引用左值
3.c++11中的右值引用,只能引用右值
int main(){
int a = 10;
int& a1 = a; //一般引用只能引用左值
//const引用既可以引用左值也可以引用右值
const int& a2 = a;
const int& a3 = 10;
//c++11中的右值引用,a4引用的不是10本体
//而是右值引用变量a4在定义的时候编译器创建了一个临时变量
//a4引用的是这个临时变量
int&& a4 = 10;
}
通过右值引用实现移动语义:
什么是移动语义?
移动语义:将一个对象中的资源移动到另一个对象中
先来看一段代码
class String
{
public:
//...
String operator+(const String& s)
{
char* pTemp = new char[strlen(_str) + strlen(s._str) + 1];
strcpy(pTemp, _str);
strcpy(pTemp + strlen(_str), s._str);
String strRet(pTemp);
return strRet;
}
// _str:在堆内存,存放拼接后结果
};
int main(){
String s1("hello");
String s2("world");
String s3(s1+s2);
}
strRet在按照值返回时,创建了一个临时对象,当此临时对象创建完成之后,strRet就会被销毁(出了函数作用域),最后使用返回的临时对象构造s3,s3构造完成之后,临时对象再被销毁,可以发现,构造s3的过程中,strRet和临时对象,都是创建了然后再销毁,相当于一共创建了三个完全相同的对象,最后只留下一个。所以以上方式对空间来说是一种浪费行为,程序效率也会降低,所以c++11引入了移动语义
c++11中通过右值引用实现移动语义,向上述String类添加移动构造
String (String&& s)
:_str(s._str)
{
s._str = nullptr;
}
strRet对象的声明周期在临时对象创建完成之后就结束了,所以strRet为将亡值,所以strRet为右值,因此在strRet构造临时对象时,会采用移动构造,通过移动构造将strRet中的资源转移到临时对象中,因为临时对象也是右值,所以在构造s3时,也会通过移动构造去构造s3,将临时对象中的资源移动到s3中,所以整个构造s3过程中,只需要创建一块堆内存就可以了,节省空间又提升了程序运行效率
注意:
1.移动构造函数的参数不能设置成const类型的右值引用,因为资源无法转移而导致移动构造失败
2.c++11中,编译器会默认生成一个移动构造,但是该移动构造为浅拷贝,所以当类中涉及到资
源管理时,需要自己显式定义自己的移动构造
右值引用可以引用左值吗?
可以,当需要右值引用去引用一个左值时,可以使用move()函数实现,需要注意的是此函数并不具有搬移资源功能,只是将一个左值转换为右值,并且被转换的左值生命周期不会因为变成了右值而改变
int main()
{
int a = 10;
int&& a1 = move(a);
String s1("hello");
String s2(move(s1));
//注意此时因为s1被转换为了右值,所以s2是通过移动构造构造的
//但是s1中的资源因为移动构造而s1变成了无效字符串
}
左值引用与右值引用总结:
右值引用做参数和做返回值减少拷贝的本质是利用了移动构造和移动赋值
左值引用和右值引用的作用都是减少拷贝
左值引用:解决的是传参过程中和返回值中的拷贝
//做参数
void push(T x) -> void push(const T& x);解决的是传参过程中减少拷贝
//做返回值
T func() -> T& func() 解决的是返回值过程中的拷贝
右值引用:解决的是传参后,,函数内部对象移动到容器空间上的问题,传值接收返回值的拷贝
//做参数
void push(T&& x),解决的是push内部不再使用拷贝构造x到容器空间上而是直接构造过去
//做返回值
T func(); 解决函数外面调用接收func()返回对象的拷贝,使用移动构造,减少拷贝