【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新增了两个默认成员函数:移动构造和移动赋值。

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

粒子群优化算法的实践 - 多个约束条件

粒子群优化算法的实践 - 多个约束条件 flyfish 粒子群优化算法的实践 - 目标函数的可视化 粒子群优化算法的实践 - 向量减法 在粒子群优化算法的代码实践中 代码写法是 #非线性约束 (x[0] - 1) ** 2 (x[1] - 1) ** 2 - 1<0 constraint_ueq (lambda x: (x[0] - 1) ** 2…

【期末考复习向】transformer的运作机制

1.transformer的encoder运作 transformer的encoder部分包括了输入和处理2大部分。首先是输入部分inputs&#xff0c;这里初始的inputs是采用独热向量进行表示的&#xff0c;随后经过word2vec等操作把独热向量&#xff08;采用独热向量的好处就是可向量是正交的&#xff0c;可以…

Centos7部署SVN

文章目录 &#xff08;1&#xff09;SVN概述&#xff08;2&#xff09;SVN与Samba共享&#xff08;3&#xff09;安装SVN&#xff08;4&#xff09;SVN搭建实例&#xff08;5&#xff09;pc连接svn服务器&#xff08;6&#xff09;svn图标所代表含义 &#xff08;1&#xff09;…

【大数据】详解 AVRO 格式

详解 AVRO 格式 1.Avro 介绍2.schema2.1 原始类型2.2 复杂类型2.2.1 Records2.2.2 Enums2.2.3 Arrays2.2.4 Maps2.2.5 Unions2.2.6 Fixed 3.Avro 的文件存储格式3.1 数据编码3.1.1 原始类型3.1.2 复杂类型 3.2 存储格式3.3 存储格式 4.小结 1.Avro 介绍 Apache Avro 是 Hadoop…

【rabbitMQ】声明队列和交换机

上一篇&#xff1a;springboot整合rabbitMQ模拟简单收发消息 https://blog.csdn.net/m0_67930426/article/details/134904766?spm1001.2014.3001.5501 相关配置环境参考上篇 springAMQP提供了几个类用来声明声明队列&#xff0c;交换机及其绑定关系 声明队列&#xff0c;…

经典策略筛选-20231213

策略1&#xff1a; 龙头战法只做最强&#xff1a;国企改革 ----四川金顶 1、十日交易内出现 涨停或 &#xff08;涨幅大于7个点且量比大于3&#xff09; 2、JDK MACD RSI OBV LWR MTM 六指标共振 3、均线多头 4、 筹码峰 &#xff08;锁仓&#xff09; 5、现价> 五日均…

C语言之文件操作(上)

C语言之文件操作&#xff08;上&#xff09; 文章目录 C语言之文件操作&#xff08;上&#xff09;1. 什么是⽂件&#xff1f;1.1 程序⽂件1.2 数据⽂件1.3 ⽂件名 2. ⼆进制⽂件和⽂本⽂件3. ⽂件的打开和关闭3.1 流和标准流3.1.1 流3.1.2 标准流 4. ⽂件指针5. 文件的打开与关…

什么是连接池?如何确认连接池的大小?

对于我们编写的几乎每个网络或移动应用程序来说&#xff0c;其底层的关键组件之一就是数据库。对于编写使用数据库且高性能且资源高效的应用程序&#xff0c;必须处理一项关键资源&#xff0c;但与 CPU、内存等不同&#xff0c;它通常不是很明显。该资源是数据库连接。 什么是…

调用Win10隐藏的语音包

起因 在做一个文本转语音的Demo的时候&#xff0c;遇到了语音包无法正确被Unity识别的问题。明明电脑上安装了语音包但是代码就是识别不出来 原因 具体也不是非常清楚&#xff0c;但是如果语言包是在的话&#xff0c;大概率是Win10系统隐藏了。 确定语言包 首先查看%windi…

VLAN详细学习

文章目录 VLAN概念VLAN种类端口VLAN工作原理以太网的三种链路类型配置 VLAN概念 一种讲局域网设备从逻辑上划分为一个个网段&#xff0c;从而实现虚拟网络的一种技术&#xff0c;这一技术主要应用于交换机中。Vlan技术是技术在以太网帧的基础上增加vlan头&#xff0c;用VLAN I…

云计算大屏,可视化云计算分析平台(云实时数据大屏PSD源文件)

大屏组件可以让UI设计师的工作更加便捷&#xff0c;使其更高效快速的完成设计任务。现分享可视化云分析系统、可视化云计算分析平台、云实时数据大屏的大屏Photoshop源文件&#xff0c;开箱即用&#xff01; 若需 更多行业 相关的大屏&#xff0c;请移步小7的另一篇文章&#…

代码随想录算法训练营第50天| 123.买卖股票的最佳时机III 188.买卖股票的最佳时机IV

JAVA代码编写 123.买卖股票的最佳时机III 给定一个数组&#xff0c;它的第 i 个元素是一支给定的股票在第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。 **注意&#xff1a;**你不能同时参与多笔交易&#xff08;你必须在再次购买前出…

记录 | ubuntu上安装fzf

在 ubuntu 上采用命令行安装 fzf 的方式行不通 指的是采用下面的方式行不通&#xff1a; sudo apt install fzf # 行不通 sudo snap install fzf --classic # 行不通正确的安装方式是&#xff1a; ● 到 fzf 的 git 仓库&#xff1a;https://github.com/junegunn/fzf/re…

aardio网页组件:webPageOperation

webPageOperation是webview的初步封装&#xff0c;用来网页填表、操作网页。可操作web.form、web.view、web.view2等浏览器组件。 使用方法 首先把webPageOperation.aardio&#xff08;源码在后面&#xff09;放到~\lib\godking目录下&#xff0c;然后新建窗口项目&#xff…

Leetcode—10.正则表达式匹配【困难】

2023每日刷题&#xff08;五十八&#xff09; Leetcode—10.正则表达式匹配 算法思想 参考题解 实现代码 class Solution { public:bool isMatch(string s, string p) {int m s.size(), n p.size();vector<vector<bool>> dp(m 1, vector<bool>(n …

基于单片机智能循迹小车仿真设计

**单片机设计介绍&#xff0c;基于单片机智能循迹小车仿真设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的智能循迹小车是一种通过传感器检测地面情况&#xff0c;并根据设定的规则进行动作控制的机器人。它使用…

信号量机制及信号量实现进程同步、互斥、前驱关系

进程互斥的四种软件实现万式&#xff08;单标志法、双标志先检查、双标志后检查、Peterson算法)进程互斥的三种硬件实现方式&#xff08;中断屏蔽方法、TS/TSL指令、Swap/XCHG指令&#xff09; 1.在双标志先检查法中&#xff0c;进入区的“检查”、“上锁”操作无法一气呵成&am…

性能测试、负载测试、压力测试之间的差异!

1、什么是性能测试 性能测试是一种用于确定计算机、网络或设备速度的测试。它通过在不同的负载场景中传递不同的参数来检查系统组件的性能。 2、什么是负载测试 负载测试是在任何应用程序或网站上模拟实际用户负载的过程。它检查应用程序在正常和高负载期间的行为。当开发项目…

CAN 三: STM32 CAN相关寄存器介绍

1、寄存器列表&#xff08;F1/F4/F7&#xff09; 寄存器名称作用CAN_MCRCAN主控制寄存器主要负责CAN工作模式的配置CAN_BTR位时序寄存器用来设置分频/TBS1/TBS2/TSWJ等参数&#xff0c;设置测试模式CAN_(T/R)IxR标识符寄存器存放(待发送/接收)的报文ID、扩展ID、IDE位及RTR位C…

[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…