当前位置: 首页 > article >正文

右值和右值引用【C++】

文章目录

  • 左值引用和右值引用简单介绍
    • 左值:
    • 右值:
    • 左值引用,引用右值
    • 右值引用,引用左值
    • 为什么强制类型转换之后,右值引用就可以引用左值了呢?
  • 右值引用的作用
    • 举个例子理解移动构造和移动赋值的作用:
      • 移动构造
      • 移动赋值:
  • 右值引用的属性
  • 万能引用
  • 完美转发

左值引用和右值引用简单介绍

左值:

左值是一个表示数据的表达式
它最主要的特点就是:
可以取地址
生命周期比较长,最短都是局部变量的生命周期
可以出现在=左边,即可以被修改

然后把地址给引用或者指针,就可以通过引用或者指针改变这个左值

左值引用就是给左值取别名
左值可以出现在=的左右两边


右值:

右值也是一个表示数据的表达式
[右值一般都是临时性的东西,是用临时的空间存储的临时变量,匿名对象等]

在这里插入图片描述

右值最主要的特点就是:
不能取地址
生命周期很短,一般就一行
不可以出现在=左边,即不能被修改

右值引用就是给右值取别名
右值只能出现在=的右边


左值引用,引用右值

在这里插入图片描述

右值引用,引用左值

在这里插入图片描述
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就返回左值引用,引用的左值



http://www.kler.cn/a/614424.html

相关文章:

  • Jmeter性能测试
  • 硅基流动的api_key可以跑通OpenManus 完整配置
  • Imgui处理glfw的鼠标键盘的方法
  • 1--当「穷举」成为艺术:CTF暴力破解漏洞技术从入门到入刑指南(知识点讲解版)
  • Python爬虫:开启数据抓取的奇幻之旅(一)
  • MyBatis基础三(注解开发)
  • RabbitMQ高级特性--重试特性
  • 28_跨域
  • 并发编程模型
  • QT登录注册模块(客户端)
  • [特殊字符]《多商户家政系统技术解析:SpringBoot+MyBatisPlus+UniApp高效实战指南》
  • 力扣刷题494. 目标和
  • Linux环境下安装部署Docker
  • (二)创建实例
  • 去噪算法大比拼
  • Vite任意文件读取漏洞:CVE-2025-30208
  • DeepSeek-R1国产大模型实战:从私有化部署到内网穿透远程使用全攻略
  • HCIP之VRRP
  • ETL与数据迁移:从传统系统到云平台的平稳过渡全攻略
  • 【磁盘扩容】linux磁盘扩容