C++11 左值 右值

目录

什么是左值?什么是左值引用?

什么是右值?什么是右值引用?

左值引用使用场景和意义

优化

右值插入

总结

完美转发

模板中的&& 万能引用

代码托管


什么是左值?什么是左值引用?

左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋

值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边定义时const修饰符后的左

值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名

i和j都可以被取地址,因此都是左值:

i和j都是左值引用

什么是右值?什么是右值引用?

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引

用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能

取地址。右值引用就是对右值的引用,给右值取别名

下面几个不可以被取地址,因此是右值:(右值可以是常数,表达式,函数):

以下几个为右值引用(两个&就是右值引用):

左值能否给右值取别名?

如下所示,不可以:

因为右值不能被修改,加引用就可以修改了,对右值来说,这是一种权力的返回广大,因此我们要加const:

右值表达式同理:

右值引用能否给左值取别名?

如下所示,不能:

但是右值可以给mov后的左值取别名:

左值引用使用场景和意义

(1)引用传参,(2)引用返回

前面我们可以看到左值引用可以引用左值和又可以引用右值,那为什么C++11还要提出右值引

用呢?是不是化蛇添足呢?下面我们来看看左值引用的短板,右值引用是如何补齐这个短板的!

场景1:

我们模拟一个string,命名空间为bit。

bitstring.cc · 孙鹏宇/孙鹏宇的第一个仓库 - 码云 - 开源中国 (gitee.com)

接下来我们在模拟的string里写一个to_string函数(请先将bit命名空间里的移动构造函数,移动赋值函数注释掉):

场景1是没办法用左值引用返回的:

强行左值引用返回:

原因:

ret是to_string()函数的局部变量,出了作用域就会销毁,我们正常返回是对ret做了个拷贝,返回的是拷贝。如果强行用引用返回,相当于把ret的别名返回,ret出了作用域就销毁了,别名自然也销毁了。

只有出了作用域变量还在,才能用左值引用返回!

ret出了作用域就会销毁,所以需要拷贝构造创建一个临时变量,返回临时变量。

临时变量返回之后s去接受,又需要一次赋值拷贝。两次拷贝代价很大:

优化

ret对象无法拯救,但是ret指向的资源可以拯救!

右值分为普通右值将亡右值列如上文出了作用域就要销毁的就叫做将亡值

对于将亡值,既然它都要快要灭亡了,那我们就直接拿它的资源来用,如下:

这种方式叫做移动构造.

交换资源比做深拷贝代价要小。

如果已经确定一个对象不会再被使用了,就可以把这个对象的资源窃取过来,移动到新的对象中,这个过程是高效的,因为,只需要拷贝指针就行,也就是只需要进行浅拷贝。然后,把原来的指针置空。

注意事项

像下面这种都会调拷贝复制:

但是如果我们用了move():

s1是左值,我们如果给move(s1),那么就会给s1打上一个将亡值的标签.那么就会掉移动构造,s1的资源就会被转移走.

如果我们不想s1的资源被转移走,那就不要轻易使用move().

移动构造的使用场景

像日期类用移动构造构造就没有意义,无非就是move年月日,实际上用拷贝就可以了.移动构造一般用于需要深拷贝的场景中.

右值插入

C++11给STL容器的 插入 接口提供了 右值插入版本,例如:
list:set:

等等。

什么情况下用左值插入,什么情况下用右值插入?

如下,list左值插入调用了一次深拷贝,list右值插入调用了两次移动构造,虽然是两次移动构造,但是代价也比一次深拷贝小

如果没有移动构造函数,右值插入会有两次深拷贝

接下来不用库的list,用我们自己模拟实现的list实现右值引用。

首先是以前写的没有右值引用功能的模拟实现list:list.h · 孙鹏宇/孙鹏宇的第一个仓库 - 码云 - 开源中国 (gitee.com)

在原先模拟实现的list基础上增加 C++11右值插入版本:

我们先用库的list指行如下操作:

(1)插入左值s1,(2)调用to_string,(3)插入字符串"222222"。

发现,只有左值插入时才会产生深拷贝,剩下的全是调移动构造:

然后我们调自己写的list进行同样的操作:

发现只调了一次移动构造,剩下全是深拷贝!

这是什么原因呢?
 

通过调试,发现调用to_string确实调用的是移动构造右值尾插:

然后我们可以看一下 insert()调的是哪个版本的insert()函数。F11进入insert()函数,如下:

我们发现竟然调了左值插入

总结

右值不能修改,但是右值的引用可以修改(右值的引用本质调用移动拷贝,如果右值引用不能被修改,怎么转移资源?),即右值的引用为左值:

这样就可以解释为什么最后调到了  左值插入:

解决方法:

我们可以move一下,就可以调 右值插入了:

运行,发现还是只调了一次移动构造:

原因:

insert()会把x传给new,new会调构造函数,构造函数是左值引用:

所以我们还需要再写一个右值引用版本的构造函数:

不能写两个全缺省的构造函数,编译器不知道调哪个,我们把右值引用全缺省构造函数改一下:

原因:

右值引用的属性是左值,我们需要再move一下把它变回右值:

完美转发

完美转发解决的问题是:无论传递的是右值还是左值,函数的参数作为一个局部变量,都会变成左值。也即是,函数内部会开辟一个空间,存储这个参数,那这个参数再函数内部就是一个左值了。如果我需要区分左值参数和右值参数,一是做两个版本函数、二是使用const type& 参数传递。这都不是很好的解决方法。

 

模板中的&& 万能引用

传统的引用来看这个是右值引用,但实际上它既可以是左值引用可以是右值引用,因为它是模板

// 模板中的&&不代表右值引用而是万能引用,其既能接收左值又能接收右值
// 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,
// 但是引用类型的唯一作用就是限制了接收的类型后续使用中都退化成了左值
// 我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发

 写一段如下代码:

 传参调用一下:

结果:

为什么全是左值引用?

因为右值的引用是左值,那么move一下呢?

也不行,我们期望保持值原本的属性,即:是左值就调左值引用是右值就调右值引用

 这个时候我们就要用到完美转发这个东西:

代码托管

右值引用和移动语义 · 孙鹏宇/孙鹏宇的第一个仓库 - 码云 - 开源中国 (gitee.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孙鹏宇.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值