前言:
我们首先汇总一下在C++11中新的变化:
1、新容器 —— unodered_xxx
2、新接口
- cbegin等,无关痛痒
- initializer_list系列的构造
- push_xxx / insert / emplace 等增加右值引用插入版本,意义重大,提高效率
- 容器新增移动构造和移动赋值,也可以减少拷贝,提高效率
毫无疑问,其中最重要的就是右值引用和移动构造赋值,接下来我们重点讲解有关知识~
一、右值引用
我们首先要清楚跟右值相对的概念,左值和左值引用。
什么是左值,什么是左值引用?
答:
左值是一个表达式,我们可以获取它的地址。一般可以对它进行赋值(加上const变成常量就修改不了)。
左值引用就是给左值取别名。
int& func2()
{const int x = 2;return x;
}int main()
{// 以下的p、b、c、*p, func2()返回值 都是左值int* p = new int(0);int b = 1;const int c = 2;const int* ptr1 = &c;int* ptr2 = &func2();printf("%p %p\n", ptr1, ptr2);return 0;
}
什么是右值,什么是右值返回?
答:
右值也是表达式,如字面常量、表达式返回值、函数返回值(不能是左值引用返回)。
右值可以出现在赋值符号的右边,但是不能出现在赋值符号的左边,右值不能取地址。
右值引用就是对右值的引用,给右值取别名。
int main()
{
double x = 1.1, y = 2.2;
// 以下几个都是常见的右值
10;
x + y;
fmin(x, y);
// 以下几个都是对右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
10 = 1;
x + y = 1;
fmin(x, y) = 1;
return 0;
}
注意右值引用是两个&,跟左值引用一个&做区分。
小总结:
语法上:
引用都是别名,不开空间,左值引用是给左值取别名,右值引用是给右值取别名。
底层:(了解)
引用是用指针实现的。左值引用是存当前左值的地址。右值引用,是把当前右值拷贝到栈上的一个临时空间,存储这个临时空间的地址。
TIP:右值引用与左值引用的交叉
int main()
{// 左值引用能否给右值取别名 不能// 但是const左值引用可以const int& r1 = func1();const int& r2 = 10;// 右值引用能否给左值取别名 不能// 但是右值引用可以给move以后的左值可以int x = 0;int&& rr1 = move(x);return 0;
}
左值引用能否给右值取别名——不能
但是const左值引用可以
右值引用能否给左值取别名——不能
但是右值引用可以给move以后的左值可以
引用的意义:
本质为了减少拷贝!
所以右值引用到底有什么用?(左值引用没有解决所有问题)
我们首先看看左值引用解决了什么问题?
1、传参的拷贝解决了
浅拷贝不用考虑,深拷贝时我们采用引用取别名的方法,将传参不用再额外开辟空间。减少拷贝
2、传返回值的问题解决了一部分
函数调用结束时,返回值仍然存在,不用开辟新空间(引用返回)
但是
局部对象(出了作用域就销毁的对象)返回的拷贝问题,只能传值返回,就存在拷贝,如果有些对象会拷贝会消耗巨大的问题没有解决!
注意深拷贝是一种极度的资源浪费,因为深拷贝过后,临时创建的对象马上又销毁。
这里就需要我们的右值引用!
C++11对右值概念的解释,细分便于理解
1、纯右值(内置类型的右值)如:10 / a + b
2、将亡值(自定义类型的右值)如:匿名对象、传值返回函数
优化之前:
解释:
原本的str是左值,但是会有拷贝构造产生的临时值,也就是右值(将亡值),这里利用将亡值的特性使用移动构造,因此是1次拷贝,1次移动。
优化之后:
直接隐式将原本的左值str move()转换变成右值,这样就可以不用再创建临时对象进行拷贝构造,直接一次移动构造就可以完成!!!
优化点:
1、将一次拷贝、一次移动合二为一,省去中间的临时对象
2、隐式的强行对move(str)识别为右值
总结:
浅拷贝的类不需要移动构造
深拷贝的类才需要移动构造
深拷贝对象传值返回只需要移动资源,代价很低
问题:右值不能改变,那怎么转移你的资源呢?
答:
右值被右值引用后,右值引用的属性是左值,可以被改变,这样资源才能被转移!
右值引用延长了资源的生命周期!!!并不是延长对象的生命周期。