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

news2024/12/24 8:47:20

一.左值与右值

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

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

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.coloradmin.cn/o/1308261.html

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

相关文章

Java 入门第四篇 集合

Java 入门第四篇 集合 一&#xff0c;什么是集合 在Java中&#xff0c;集合&#xff08;Collection&#xff09;是一种用于存储和操作一组对象的容器类。它提供了一系列的方法和功能&#xff0c;用于方便地管理和操作对象的集合。集合框架是Java中非常重要和常用的一部分&…

C++进阶篇9---类型转换

C语言中的类型转换 在C语言中&#xff0c;如果赋值运算符左右两侧类型不同&#xff0c;或者形参与实参类型不匹配&#xff0c;或者返回值类型与 接收返回值类型不一致时&#xff0c;就需要发生类型转化&#xff0c;C语言中总共有两种形式的类型转换&#xff1a;隐式类型 转换和…

参数学习——糖果问题(人工智能期末复习)

之前看了好久都不知道这题咋写&#xff0c;后来看了这篇机器智能-高频问题&#xff1a;糖果问题&#xff0c;大概看明白了&#xff0c;其实主要围绕着这两个公式 光看公式也看不懂&#xff0c;还是要结合题目来 己知有草莓味和酸橙味两种类型的糖果&#xff0c;分别放入5种不同…

深入了解—C++11特性

目录 一、 C11简介 二、初始化列表 2.1 C98中{}的初始化问题 2.2 内置类型的列表初始化 2.3 自定义类型的列表初始化 2.3.1. 标准库支持单个对象的列表初始化 2.3.2. 多个对象的列表初始化 三、变量类型推导 3.1 为什么需要类型推导 3.2 decltype类型推导 3.2.1. 推…

SIM初始化流程

ATR ATR(Answer To Reset)&#xff1a;复位应答信号&#xff0c;有SIM卡传输给终端&#xff0c;包括SIM卡自身的一些信息&#xff0c;比如支持的传输速率&#xff0c;传输模式等。 SIM卡的ATR代表"Answer to Reset"&#xff0c;即复位响应。当SIM卡被插入设备中时…

Java的String类常用方法 |StringBuilder和StringBuffer

文章目录 String类常用方法字符串查找转化字符串替换字符串拆分字符串截取其他操作方法 字符串的不可变性StringBuilder和StringBufferStringBuilder的介绍面试题 String类常用方法 字符串查找 方法功能char charAt(int index)返回index位置上字符&#xff0c;如果index为负数…

深度解读分布式事务Seata入门到实践 -尚马教育

目录 一、事务的回顾1、什么是事务2、事务的特性3、事务的隔离级别4、事务的分类 二、分布式事务1、什么是分布式事务2、分布式事务产生的背景3、分布式事务产生的场景4、分布式事务理论4.1 CAP理论4.2 Base理论 5、分布式事务的解决方案 三、强一致性介绍3.1 基本理解3.2 DTP模…

arm-none-eabi-gcc not find

解决办法&#xff1a;安装&#xff1a;gcc-arm-none-eabi sudo apt install gcc-arm-none-eabi; 如果上边解决问题了就不用管了&#xff0c;如果解决不了&#xff0c;加上下面这句试试运气&#xff1a; $ sudo apt-get install lsb-core看吧方正我是运气还不错&#xff0c;感…

环境搭建及源码运行_java环境搭建_mysql安装

1、介绍 MySQL是一个关系型数据库管理系统&#xff0c;由瑞典MySQL AB 公司开发&#xff0c;属于 Oracle旗下产品。MySQL是最流行的关系型数据库管理系统之一 1、源码中涉及到的表&#xff1a;mysql 表&#xff1a;订单、意见反馈、用户基础信息、商品、购物车等表 2、订单属于…

Qt/C++视频监控安卓版/多通道显示视频画面/录像存储/视频播放安卓版/ffmpeg安卓

一、前言 随着监控行业的发展&#xff0c;越来越多的用户场景是需要在手机上查看监控&#xff0c;而之前主要的监控系统都是在PC端&#xff0c;毕竟PC端屏幕大&#xff0c;能够看到的画面多&#xff0c;解码性能也强劲。早期的手机估计性能弱鸡&#xff0c;而现在的手机性能不…

自动反冲洗过滤器直通式工作原理介绍和附反冲洗原理图动画讲解

​ 1&#xff1a;自动反冲洗过滤器直通式设备介绍 自动反冲洗过滤器是一种先进、高效且易操作的精密设备&#xff0c;广泛应用于冶金、化工、石油、造纸、医药、食品、采矿、电力、城市给水等领域。 在过滤过程中&#xff0c;待过滤的水由进水口进入过滤器机体&#xff0c;流…

Java版工程行业管理系统源码-专业的工程管理软件-提供一站式服务—鸿鹄工程管理系统

鸿鹄工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离构建工程项目管理系统 项目背景 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大。为了提高工程管理效率、减轻劳动强度、提高信息处理速度和准确性&#xff0c;公司对内部工程管理的提…

Python(七)操作JSON

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一波电子书籍资料&#xff0c;包含《Effective Java中文版 第2版》《深入JAVA虚拟机》&#xff0c;《重构改善既有代码设计》&#xff0c;《MySQL高性能-第3版》&…

基于Java SSM框架实现个性化影片推荐系统项目【项目源码+论文说明】计算机毕业设计

基于java的SSM框架实现个性化影片推荐系统演示 摘要 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;个性化影片推荐系统当然也不能排除在外。个性化影片推荐系统是以实际运用…

【源码解析】flink sql执行源码概述:flink sql执行过程中有哪些阶段,这些阶段的源码大概位置在哪里

文章目录 一. sql执行流程源码分析1. Sql语句解析成语法树阶段&#xff08;SQL - > SqlNode&#xff09;2. SqlNode 验证&#xff08;SqlNode – >Operation&#xff09;3. 语义分析&#xff08;Operation - > RelNode&#xff09;4. 优化阶段&#xff08;RelNode - &…

uni-app 一些实用的页面模板

时间倒计时 <!-- 时间倒计时 --> <template><view class"container"><view class"flex-row time-box"><view class"time-item">{{ laveTimeList[0] }}</view><text>天</text><view class&qu…

喜讯 | 同立海源生物入选2023年国创中心细胞疗法“揭榜挂帅”技术攻关项目

近日&#xff0c;2023年国家生物药技术创新中心细胞疗法“揭榜挂帅”技术攻关拟立项目名单公示&#xff0c;北京同立海源生物科技有限公司&#xff08;简称“同立海源生物”&#xff09;参评的 “细胞分选激活磁珠研发项目” 凭借公司多年在细胞分选磁珠领域的技术沉淀和创新性…

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

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

K8S(五)—命名空间与资源配额

目录 命名空间(Namespace)命令计算资源配额创建命名空间绑定一个ResourceQuota资源将命名空间和资源限制对象进行绑定尝试创建第二个 Pod查看ResourceQuota 绑定第二个ResourceQuota为命名空间配置默认的 CPU 、memory请求和限制&#xff08;1&#xff09;Pod 中所有容器都没有…

样本空间的一个划分

假设试验E的样本空间为S&#xff0c;为E的一组事件&#xff0c;如果这组事件满足如下条件&#xff1a; &#xff0c;其中&#xff0c;&#xff0c;即事件两两之间互不相容 那么就称为样本空间S的一个划分。