【C++11】右值引用与移动语义

一.左值与右值

左值:可以取地址的表示数据的表达式,左值可以出现在赋值符号左边

右值:不能取地址的表示数据的表达式,右值不能出现在赋值符号左边

int fun()
{return 0;
}
int main()
{int a = 0;//a->左值const int b = 1;//b->左值int* p = &a;//*p->左值a + b;//右值func();//右值10;//右值
}

二.左值引用与右值引用 

左值引用:给左值取的别名,符号:type&

  1. 左值引用只能引用左值,不能引用右值
  2. 但是const左值引用既可引用左值,也可引用右值

右值引用:给右值取的别名,,符号:type&&0

  1. 右值引用只能右值,不能引用左值
  2. 但是右值引用可以move以后的左值

无论左值引用还是右值引用,都是在取别名,理论上来说不会开辟额外的空间

int a = 0, b = 0;
int& ref1 = a;//左值引用给左值取别名
const int& ref2 = a + b;//临时对象具有常性,左值引用想要绑定右值要将上const
int&& ref3 = a + b;//右值引用给右值取别名
int&& ref4 = move(a);//右值引用给move以后的左值取别名

说明:右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址。例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用。

三.使用引用减少拷贝

当函数传参或者传返回值时,传的是自定义类型对象,如果处理不当,将会发生多次拷贝,尤其是需要深拷贝的类,大大降低效率,使用引用就可以有效解决这些问题。

1.左值引用减少拷贝

  1. 引用传参
  2. 传引用返回

左值引用的短板:当函数返回对象是局部对象,出作用域就会被销毁,此时只能传值返回,倘若该对象需要深拷贝,付出的代价是很大的。

为了解决这一问题,右值引用在C++11应运而生。

2.右值引用减少拷贝

(1)深拷贝的类DeepCopy

class DeepCopy
{
public:DeepCopy(int* p):_p(p){}~DeepCopy(){delete[] _p;}
private:int* _p;
};int main()
{DeepCopy d1 (new int[5]{ 1, 2, 3, 4, 5 });DeepCopy d2 = d1;return 0;
}

DeepCopy类就是一个需要深拷贝的类,因为它的成员着管理一块资源,析构时需要释放资源。如果使用编译器提供的浅拷贝构造,会导致同一块空间释放两次,因此需要我们自己提供深拷贝构造,同样,赋值也需要自己提供。

    DeepCopy(const DeepCopy& d){_p = new int[d._n];_n = d._n;for (int i = 0; i < _n; i++){_p[i] = d._p[i];}cout << "拷贝构造" << endl;}DeepCopy& operator=(const DeepCopy& d){if (this != &d){delete[] _p;_n = d._n;_p = new int[_n];for (int i = 0; i < _n; i++){_p[i] = d._p[i];}}cout << "拷贝赋值" << endl;return *this;}

(2)传值返回发生拷贝构造

传值返回的场景:

DeepCopy Fun()
{DeepCopy d(new int[3]{ 1,2,3 }, 3);return d;
}
int main()
{DeepCopy d1 = Fun();return 0;
}

 

整个过程如下:

由于是传值返回,所以先用d1拷贝构造一个临时对象,再用临时对象拷贝构造d。本来是分两步的,但连续的构造被编译器优化成一步。

请注意:临时对象是右值,const左值引用可以接收右值,故可以匹配拷贝构造函数

(3)传值返回发生移动构造 

我们可以将右值分为两类:

  1. 纯右值:内置类型的右值,包括字面常量,表达式结果,函数返回值等等
  2. 将亡值:自定义类型的右值,即函数返回值,如它的名字一样,它不会再被使用,马上就会被销毁。

 对于将亡值的拷贝,我们不用照着它的模版重新生成一份,而是直接转移它的资源,这样代价会小很多。因此,我们可以针对这种将亡值专门设计一个构造函数来转移资源,这种构造函数叫移动构造

    //DeepCopy中增加:DeepCopy(DeepCopy&& d){_p = nullptr;std::swap(_p, d._p);std::swap(_n, d._n);cout << "移动构造" << endl;}int main()
{DeepCopy d1(new int[5]{ 1,2,3,4,5 }, 5);d1 = Fun();return 0;
}

编译器会将返回的d1对象特殊处理,将它识别为右值,所以这里用一个右值构造临时对象。右值既虽然可以被const左值引用接收,但右值引用是更合适的,所以会匹配移动构造。同理,临时对象也是个将亡值,所以匹配的是移动构造。两次连续的构造会被编译器优化成一次移动构造。

不仅有移动构造,还有移动赋值:

    //DeepCopy中增加:	DeepCopy& operator=(DeepCopy&& d){if (this != &d){std::swap(_p, d._p);std::swap(_n, d._n);}cout << "移动赋值" << endl;return *this;}
//临时对象的资源不仅被转移走了,还得到了*this不要的资源,析构时会释放掉int main()
{DeepCopy d1(new int[5]{ 1,2,3,4,5 }, 5);d1 = Fun();return 0;
}

3.总结

引用的意义就在于减少拷贝

左值引用:直接减少拷贝:
1.引用传参 2.传引用返回

但有些场景不能传引用返回(函数内的局部对象),因此还是无法避免深拷贝
右值引用:间接减少拷贝
和const左值引用进行区分,传将亡对象拷贝时匹配移动构造函数,直接转移资源

四.完美转发

(1)属性退化

一个右值引用与右值绑定之后,这个引用的属性是左值。换句话说,右值被右值引用之后属性退化成了左值,从原来的不可修改变为可修改。如果你不想它被修改,可以用const修饰引用,但它仍然是一个左值。

从底层角度来看,实际是右值引用使数据的存储位置发生改变,可以取到数据的地址。

这也能够解释为什么移动构造或移动赋值函数中,能够转移将亡对象资源的原因,因为它的属性退化成了左值,可以被修改。

(2)万能引用

模版参数+&&=万能引用:无论是左值还是右值,无论是const还是非const,都能接收

我们可以用万能引用验证第(1)点的结论 

void fun(int& x)
{cout << "void fun(int& x)左值" << endl;
}void fun(const int& x)
{cout << "void fun(const int& x)const左值" << endl;
}void fun(int&& x)
{cout << "void fun(int&& x)右值" << endl;
}
void fun(const int&& x)
{cout << "void fun(const int&& x)const右值" << endl;
}void PerfectForward(T&& t)//T&& 万能引用
{fun(t);
}int main()
{int a = 10;PerfectForward(a);//左值PerfectForward(10);//右值const int b = 8;PerfectForward(b);//const左值PerfectForward(move(b));//const右值return 0;
}

 

(3)完美转发

如果想要在传递过程中保持对象的左值或右值属性不变,可以使用完美转发std::forward<T>(T是对象的类型)

 将上面的fun函数稍作修改:

void PerfectForward(T&& t)//T&& 万能引用
{fun(forward<T>(t));
}

 五.C++11新增的默认成员函数

C++11新增了两个默认成员函数:移动构造和移动赋值。

如果没有实现移动构造,且拷贝构造,拷贝赋值重载和析构函数都没有实现,那么编译器会生成移动构造函数,对于内置类型逐字节拷贝;对于自定义类型,如果有移动构造则去调用移动构造,没有就退而且其次,调用拷贝构造。

移动赋值的生成规则类似。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/219226.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【lesson14】MySQL表的基本查询(1)

文章目录 表的基本操作介绍retrieveselect列建表基本测试 where子句建表基本测试 表的基本操作介绍 CRUD : Create(创建), Retrieve(读取)&#xff0c;Update(更新)&#xff0c;Delete&#xff08;删除&#xff09; retrieve select列 建表 基本测试 插入数据 全列查询 …

利用断路器状态统计sentinel熔断次数

最近项目需要sentinel熔断时记录熔断的次数&#xff0c;在经过一阵搜索后决定利用断路器的状态变化来实现此功能 然而&#xff0c;遇到了这样的一个情况&#xff0c;断路器的状态在第一次熔断时正常从close–>open&#xff0c;但在后续&#xff08;熔断时间内blocked或者熔断…

RocketMQ的延迟消息是如何实现的❓

RocketMQ 作为一款强大的分布式消息中间件&#xff0c;提供了丰富的功能&#xff0c;其中之一就是延迟消息。在本篇博客中&#xff0c;我们将深入探讨 RocketMQ 延迟消息的实现机制&#xff0c;了解消息的定时投递和消费流程。 1. 定时消息的发送 RocketMQ 实现延迟消息的第一…

linux应用软件下载站收集

一、这是一个别人问题帖&#xff0c;里面有很多下载站点。 谁知道可以自由下载Linux软件的论坛或者平台&#xff1f;类似52破解论坛。国内国外都可以&#xff0c;我在搜索引擎找不到&#xff1f; - 知乎

2023年度影响力出海品牌传音移动互联:开放合作 赋能更多中国企业高效出海

伴随着全球化的脚步&#xff0c;出海成为许多中国企业的“必选项”&#xff0c;与之配套的出海服务相关业务也得到了极大的发展。近日&#xff0c;第五届鲸鸣奖颁奖典礼上&#xff0c;传音移动互联凭借为企业提供高效优质的出海解决方案&#xff0c;荣获鲸鸣奖“2023年度影响力…

SpringBoot 引入nacos 【最新 | 可运行】

SpringBoot 引入nacos 首先要了解在 Springboot 中只支持那些 Springboot 的版本&#xff08;我真的被这个搞死了&#xff09;,可以如下图参考&#xff1a; 下面我们就开始吧 下载 Nacos nacos 下载地址&#xff0c;这里可以选择你要下载的版本&#xff0c;我选择下载了2.2.…

<JavaEE> 文件IO -- 数据流和文件内容操作(Reader 和 Writer 、InputStream 和 OutputStream)

目录 一、数据流概述 二、流的关闭 2.1 使用 close() 方法 2.2 使用 try-finally 2.3 使用 try-with-resources 三、字符流的读写 3.1 Reader 类 3.2 Writer 类 四、字节流的读写 4.1 InputStream 类 4.2 OutputStream 类 一、数据流概述 1&#xff09;在 Java 中&…

[c]零钱兑换

题目比较简单&#xff0c;看答案就能看懂什么意思 #include<stdio.h> int main() {int count 0;int n;scanf("%d", &n);for (int i 0; i < n; i){for (int k 0; k <n/2; k){for (int j 0; j < n/5 ; j){if (i 2 * k 5 * j n){count;}}}}p…

【Python基础】迭代器

文章目录 [toc]什么是迭代可迭代对象判断数据类型是否是可迭代类型 迭代器对可迭代对象进行迭代的本质获取可迭代对象的迭代器通过迭代器获取数据StopIteration异常 自定义迭代器__iter__()方法__next__()方法判断数据类型是否是可迭代类型自定义迭代器案例分离模式整合模式 fo…

这套软件测试技巧|软测经典面试题真的有用,今天面试大部分都遇到了!!!

祝同学们都能够顺利找到心仪的工作拿高薪&#xff0c;废话不多说&#xff0c;下面上题了~ 46、您以往是否曾经从事过性能测试工作&#xff1f;如果有&#xff0c;请尽可能的详细描述您以往的性能测试工作的完整过程。 &#xff08;以自己最熟悉的性能测试项目为例&#xff09; …

【ARM Trace32(劳特巴赫) 高级篇 20 -- SNOOPer 使用介绍】

请阅读【Trace32 ARM 专栏导读】 文章目录 Trace32 SNOOPer 介绍SNOOPer 主要功能:SNOOPer 使用场景SNOOPer.ERRORSTOPSNOOPer.ModeSNOOPer.PCSNOOPer.RateSNOOPer.SELectSNOOPer.SIZESNOOPer.TDelaySNOOPer.TOutSNOOPer.TValueSNOOPer PC 采样Trace32 SNOOPer 介绍 在 Laut…

什么是面向切片编程?

面向切片&#xff08;Aspect-oriented Programming&#xff0c;AOP&#xff09;是一种软件开发的方法论&#xff0c;它旨在通过将横切关注点从主要业务逻辑中分离出来&#xff0c;提供更高层次的模块化和可维护性。它通过将跨越多个对象、类或组件的共同功能&#xff08;称为横…

可惜+悲伤+唉=emmo...

拟合曲线&#xff1a; 参考论文&#xff1a;黄河清.NURBS曲面逆向造型关键算法的研究与应用 [D].西北工业大学,2004 三次NURBS曲线控制点的计算 首先给出拟合曲线的具体步骤&#xff1a; 1、节点矢量的求解方法为&#xff1a; 采用积累弦长参数化法&#xff0c;即&#xff1…

应用ICP-MS实验PFA烧杯耐腐蚀带刻度反应杯的特点分析

聚四氟&#xff08;PFA&#xff09;烧杯可用于痕量分析、同位素分析等实验&#xff0c;ICP-MS实验室适用。半导体、多晶硅、光伏电子 锂电池行业均适用。杯体刻度清晰&#xff0c;方便观察&#xff0c;尖嘴方便倾倒溶液。 可溶性聚四氟乙烯烧杯特性&#xff1a; 1、透明&…

如何定制专属app:定制app教程

《如何定制专属app&#xff1a;探索app定制的秘密》 什么是app定制 在当今数字化时代&#xff0c;应用程序&#xff08;app&#xff09;已经渗透到我们生活的方方面面&#xff0c;从社交娱乐到工作学习&#xff0c;app无处不在&#xff0c;尽管市场上有着众多的app供我们选择…

(企业 / 公司项目)微服务项目解决跨域问题:

前后端分离项目中前端出现了跨域的问题 在网关模块配置文件中添加 配置 application.properties # 允许请求来源&#xff08;老版本叫allowedOrigin&#xff09; spring.cloud.gateway.globalcors.cors-configurations.[/**].allowedOriginPatterns* # 允许携带的头信息 spri…

经典基本电路

USB电路 USB差分走线的阻抗为90欧:差分对10mil宽的走线以及5mil的间距,两边包地15/20mil以上厚度(SI9000计算阻抗) USB2.0接口电路&#xff1a; USB3.0接口电路&#xff1a; USB HUB电路: HDMI电路 HDMI差分走线的阻抗为100欧:差分对6mil宽的走线以及5mil的间距,两边包地15/20…

PADS的使用

目录 一、PADS常规操作1.1 常用快捷键1.2 Logic相关设置 二、Logic库操作2.1 逻辑库2.2 封装库2.3 元件库 一、PADS常规操作 PADS安装好了之后&#xff0c;主要使用下面三个软件&#xff1a; Logic&#xff1a;画原理图文件&#xff0c;也可以使用OrCAD导入Router&#xff1a;…

解决maven报错 ‘parent.relativePath‘ of POM

错误提示 parent.relativePath of POM io.renren:renren-fast:3.0.0 (D:\wzyProjets\gulimail\renren-fast\pom.xml) points at com.wzy.gulimail:gulimail instead of org.springframework.boot:spring-boot-starter-parent, please verify your project structure错误分析 子…

windows10下jdk安装

文章目录 windows10下jdk安装说明what安装包下载执行安装包验证是否安装成功 windows10下jdk安装 说明 操作系统&#xff1a;windows10 版本&#xff1a;1.8 what JDK(Java Development Kit) 是 Java 语言的软件开发工具包 安装包下载 https://www.oracle.com/java/techn…