文章目录
- 左值引用和右值引用简单介绍
- 左值:
- 右值:
- 左值引用,引用右值
- 右值引用,引用左值
- 为什么强制类型转换之后,右值引用就可以引用左值了呢?
- 右值引用的作用
- 举个例子理解移动构造和移动赋值的作用:
- 移动构造
- 移动赋值:
- 右值引用的属性
- 万能引用
- 完美转发
左值引用和右值引用简单介绍
左值:
左值是一个表示数据的表达式
它最主要的特点就是:
①可以取地址
②生命周期比较长,最短都是局部变量的生命周期
③可以出现在=左边,即可以被修改
然后把地址给引用或者指针,就可以通过引用或者指针改变这个左值
左值引用就是给左值取别名
左值可以出现在=的左右两边
右值:
右值也是一个表示数据的表达式
[右值一般都是临时性的东西,是用临时的空间存储的临时变量,匿名对象等
]
例
右值最主要的特点就是:
①不能取地址
②生命周期很短,一般就一行
③不可以出现在=左边,即不能被修改
右值引用就是给右值取别名
右值只能出现在=的右边
左值引用,引用右值
右值引用,引用左值
move本质就是强制类型转换
[move不会改变它的()里面的参数的类型,只是它的返回值是右值类型的而已]
为什么强制类型转换之后,右值引用就可以引用左值了呢?
这是因为底层[汇编层次]里面是没有左值和右值的区别的
底层里面甚至都没有变量名这些用合法标识符定义名称这个概念,变量名都是给人看的,机器不用看也看不懂
只有地址和这个地址对应的空间,只需要从这些空间里面取值
右值只是在语法层面上不可以取地址
因为右值也开了空间,开了空间这个空间就有自己对应的地址
底层实现的时候是肯定可以取地址的,不然就拿不到里面的值了
左值引用和右值引用,底层实现的时候是没有区别的
都是开一个4(8)字节大小空间存储引用的变量的地址
在使用引用的语法的时候,编译器自动帮我们解引用
所以我们只要在语法层面通过了编译器的检查
右值引用指能引用右值,那就把左值的类型强制类型转换成右值类型
通过了之后左值引用和右值引用底层实现因为是一样的,所以就没有问题了
右值引用的作用
就是减少深拷贝
一般体现在在移动构造和是移动赋值上
即:
调用移动构造/移动赋值时
①如果是内置类型最简单的就是浅拷贝了,没法再优化了
②如果是自定义类型,那就再调用它的移动构造呗
③如果是申请的资源(堆区资源等)此时就直接交换它们指向资源的指针就可以了
所以移动构造/移动赋值可以减少深拷贝
举个例子理解移动构造和移动赋值的作用:
如果一个函数的返回值是string
的对象,而且是传值返回
,函数外面有一个对象接收这个函数的返回值
此时
语法上:
①函数结束之前,先用要返回的string对象拷贝构造出一个临时对象
②函数调用结束之后,再用临时对象拷贝构造出函数外接收它的目标对象
编译器优化之后:
函数调用结束之前,直接使用要返回的对象拷贝构造出接收它的目标对象
所以即使编译器再怎么优化,也最少还需要一次深拷贝,才能完成返回并接收返回值
此时就可以使用
用右值引用写出来的移动构造
移动构造
一般都移动构造就是:
就是用swap去抢临时对象(右值)的资源
为什么可以直接swap抢夺返回值的资源呢?
因为返回对象的时候构造的对象是右值,是临时对象,只要出了函数的作用域就一定会销毁
既然他一定会销毁
那为什么还要花时间拷贝他的资源,直接用swap抢过来(交换资源指针的指向
)就可以了
即:
有了移动构造之后:
移动赋值:
移动赋值的实现:也是用swap去抢临时对象(右值)的资源
为什么不是浅拷贝右值,必须swap?
因为:
如果浅拷贝的话,右值里的成员指针还指向他的资源,右值析构的时候就会把资源释放掉
这个时候不仅没有抢夺资源成功,还会野指针
只有swap了,右值里面的成员指针,就指向了我不要的资源,或者指了空
所以
①如果外面接收返回值的对象是已经创建了的对象,这个时候就调用移动赋值
②如果是创建一个对象去接收传值返回的返回值,就调用移动构造
所以有了移动构造和移动赋值之后
就可以放心大胆地使用传值返回了, 因为swap的代价非常低,是O(1)
右值引用的作用还体现在插入push,insert等传进去的参数会在函数体中被深拷贝或者传值传参等
即只要有深拷贝的地方就有用
例
上图的push_back也能根据传入的是左值还是右值,调用不同的重载
插入s1的时候为什么会深拷贝?
因为插入的底层实现,是把对象的拷贝插进去,不是把对象本体插进去
但是如果是临时对象的话,因为他的生命周期只有一行(句),所以直接把他的资源抢走也没问题
右值引用的属性
右值就是右值
但是右值引用是左值
为什么右值引用是左值?
右值不仅不可以取地址,它还有一个特性是不能被修改[即前面提到的临时变量具有常属性
]
因为
①右值引用可以出现在=的左边
所以右值引用要是可以修改的
而且swap使用的是就是右值引用,如果右值引用不能修改,那就没有办法修改右值中资源指针的指向,就没办法抢夺右值的资源了
注意:
swap没有右值引用的版本,因为不需要,写了反而可能误操作
一般情况下是不会把右值传给swap的,因为右值的设定就是不能修改
移动构造/移动赋值也是把右值引用传给swap,右值引用也是左值
右值引用是左值,所以移动构造/移动赋值时调用swap的时候,传给swap的就是左值
②右值引用生命周期比较长
③右值引用可以取地址
所以
右值引用作为函数参数传递的时候,它是作为左值传递过去的,编译器匹配也是匹配左值对应的函数
例
如果调用的是
list<string> a;
a.push_back("xxxxx");
这个时候他调用的确实是下图第2个参数
是右值引用的push_back,因为他传入的"xxxx"会被编译器自动优化为右值
但再下一层就不一样了
因为接收右值的x是右值引用
所以x是左值
所以push back里面传给insert的x是左值
所以他这个时候匹配的insert是上图第一个
,左值引用对应的重载
所以如果要让push_back里面调用到的insert匹配到的是右值引用的重载,传参的时候就得把右值引用先变成右值
就得用一下的两种方法中的一个
①move一下,即push_back(move(x))
②完美转发一下,即push_back(forward(x))
万能引用
语法:
template<class T1,class T2,……>
返回值 函数名(T1&&,T2&&,……)
{
函数体
}
例
注意:
函数模板里面的&&,不是
表示右值引用,而是万能引用
即如果传进来的参数的类型是
①左值,就T&&就实例化成对应的左值引用
②右值,就T&&就实例化成对应的右值引用
但其实这个时候还有一个问题:
如果这个函数模板还要调用其他的函数(函数模板)的时候,传参怎么传?
就像上图中的
如果函数模板还要调用fun
这个时候我们期望函数模板要做到的是
①如果传进来的是左值,那么传给fun的也应该是左值
②如果传进来的是右值,那么传给fun就应该是右值
此时就有一个问题
右值传给函数模板之后,右值引用接收了右值
但右值引用本身是左值
此时如果直接把右值引用传给fun,就相当于是传给了它左值,这于我们的期望不符
这个时候就要使用完美转发了
例
完美转发
forward()
forward是一个函数模板
作用:
①传给他一个右值引用,forward就返回右值引用,引用的右值
②传给他一个左值引用,forward就返回左值引用,引用的左值