C++11(右值)
右值引用和移动语义
传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。
右值引入
在以前我们知道有引用的语法,我们通常叫做左值引用,那么什么是左值呢?可以看下边的几个例子
1 | int main() |
可以看到,左值,可以获取它的地址+可以对它赋值。当然定义时用const修饰的左值,也不能对他赋值,但是可以取地址。左值引用呢,就是给左值的引用。
知道左值后,那么什么是右值呢?右值的形式是什么?右值有什么实际价值?看几个例子
1 | int main() |
以上几个例子都是右值,右值也是一个表达数据的表达式,如字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号右边,但是不能出现在赋值符号的左边,右值不能取地址。
右值引用就是对右值的引用,给右值取别名
左值右值
左值引用可以引用右值吗?右值引用可以引用右值吗?
1 | // 有条件的支持 |
这里注意:
11是字面常量,Func函数参数列表是个左值引用,所以会报错,两种修改方法:
- Func(const T& x)
- Func(T&& x)
第一种方法:const的左值引用可以接收右值
1 | // x既能接收左值,也能接收右值 |
左右值总结
左值引用总结
- 左值引用只能引用左值。
- 但是const左值引用既可引用左值,也可引用右值。
右值引用总结
- 右值引用只能引用右值,不能引用左值
- 右值引用可以引用move之后的左值
注意
rr1和rr2可以取地址了,它们是左值了。
左值的不足
引用的价值:减少拷贝
左值引用解决哪些问题?
- 做参数。a、减少拷贝,提高效率 b、做输出型参数
- 做返回值。 a、减少拷贝,提高效率 b、引用返回,可以修改返回对象(比如:operator[])
但是,C++98的左值引用面向下边的场景很难进行处理:
右边的写法虽然解决了问题,但是并不是太符合使用习惯
具体例子
不加移动构造的string
加移动构造的string类
不加移动构造移动赋值的string
加移动构造移动赋值的string类
总结
移动构造和移动赋值解决了传值返回这些类型对象的问题,STL的各个容器在C++11增加移动构造和移动赋值。移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。
完美转发
模板中&&万能引用
在模板中,&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值,但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值。
先看个例子:
1 | void Fun(int& x) { cout << "左值引用" << endl; } |
在我们的预期中,Func函数应该是,左值打印左值,右值打印右值,但是运行结果确如下图所示:
这里可以看到,所有的都成了左值引用,根本没有调用右值引用的版本,这个就是引用折叠的问题。
std::forward
我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用完美转发。
针对上边的代码只需要将,T类型的参数t完美转发一下就可以了,std::forward 完美转发在传参的过程中保留对象原生类型属性
1 | template<typename T> |
运行结果如下:
可以看到完美转发的效果,左值调用左值,右值调用右值。
实际应用例子
在我们模拟实现的list中测试,移动构造
list插入"world"(右值),代码调用层级如下
1 | void push_back(T&& x) |
最后的节点的构造,会调用string类的构造,所以那里同样需要完美转发,调用了string类的右值构造
1 | string(string&& s) |