详解c++移动构造函数和移动赋值运算符在代码性能中起的作用

news2024/11/19 7:27:17

对象移动

对象移动,就是把一个不想用了的对象A中的一些有用的数据提取出来,在构建新对象B的时候就不需要重新构建对象中的所有数据——从不想用了的对象A中提取出来的有用数据在构建对象B时都可以拿来使用。 

我们知道,拷贝构造函数、拷贝赋值运算符等,对对象复制的成本是很高的,尤其是容器,里面如有几千个元素,那么如果对这个容器对象进行复制,里面的元素都要逐个复制,非常影响程序运行效率。

为此,提出了移动构造函数和移动赋值运算符的概念,显然,移动这件事效率会很高,比复制效率高得多,如果源对象A不再使用,那么,直接把源对象A中的某些new出来的数据移动给目标对象B,那就相当于数据还是这一堆数据,只是属主换了另外一个人,这种数据移动的效率,显然比数据复制就高,甚至某些情况下会高很多。

如果复制数据,如要把对象A复制给对象B,那对象A里面的数据还能使用,但如果把对象A(实际上是对象A中部分数据)移动给对象B对象A的数据就会出现残缺),那显然对象A就不能再被使用,否则因为数据的残缺可能会导致出现问题

移动构造函数的语法格式

我们知道,拷贝构造函数的语法格式如下,与普通构造函数的区别仅在于其参数的类型是const引用类型

	tempVal(const tempVal& t) :v1(t.v1), v2(t.v2) 
	{
		cout << "调用拷贝构造函数" << endl;
		
	}

与之相似,移动构造函数的语法格式也仅仅是在参数类型上做了修改,将const引用更换为右值引用即可

也就是

	tempVal(const tempVal&& t) :v1(t.v1), v2(t.v2) {};

有关右值引用及其与临时对象的关系可参考

c++临时对象的探讨及右值引用在临时对象中的作用-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/qq_58158950/article/details/135490225?spm=1001.2014.3001.5502

左值、右值、左值引用与右值引用-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/qq_58158950/article/details/134299061?spm=1001.2014.3001.5502

移动构造函数作用演示

接下来我们通过代码来清晰的看到移动构造函数是如何实现代码性能提升的

我们首先定义一个类B

  • 成员变量:int型的m_pm
  • 成员函数:一个构造函数一个拷贝构造一个析构函数
class B
{
public:
	int m_pm;
	B(int m=0):m_pm(m)
	{
		cout<<"类B的构造函数调用了"<<endl;
	}
	B(const B& b):m_pm(b.m_pm)
	{
		cout<<"类B的拷贝构造函数调用了"<<endl;
	}

	virtual ~B()
	{
		cout<<"类B的析构函数调用了"<<endl;
	}
};

接着我们定义一个类A

  • 成员变量:类B的指针成员m_pb
  • 成员函数:构造函数、深拷贝构造函数和析构函数

最后我们再定义一个简单的函数

static A getA()
{
	A a;
	return a;
}

在测试函数中调用该函数,并运行观察结果(注意使用-fno-elide-constructors关闭编译器的优化选项

void test()
{
	A a=getA();
}

观察运行结果可以看到,系统一共执行了一次普通构造函数和两次拷贝构造函数的调用,其中

  • 普通构造函数:是为了创建getA函数中的局部对象A
  • 第一个拷贝构造函数:是getA函数返回时生成的临时对象
    (有关临时对象的生成参考c++临时对象的探讨及相关性能提升-CSDN博客)
  • 第二个拷贝构造函数:测试函数test中getA返回的临时对象拷贝给test中的a引起

实验一

接下来,我们定义一个移动构造函数,再次实验观察运行结果

	A(A&& tmpa) noexcept:m_pb(tmpa.m_pb) 
	{
		tmpa.m_pb=nullptr;
		cout<<"类A的移动构造函数调用了"<<endl;
	}

注意,该移动构造函数与普通构造函数的区别

  • 参数类型
    • 移动构造函数的参数类型为右值引用
    • 拷贝构造函数的参数类型为常量引用(左值引用)
  • 具体实现
    • 移动构造函数的实现是直接将指针所指向的地址进行交接(浅拷贝),无需重新开辟一块新的内存
    • 拷贝构造函数的实现则是深拷贝,需要首先申请一块新的内存,之后再把待拷贝对象的内容复制到新申请的内存中

因此,单从实现上看,移动构造函数就比拷贝构造函数节省了内存资源的使用,而之所以移动构造的参数为右值引用,也是为了实现移动构造函数这种特性而产生的

接下来我们再次关闭编译器的优化选项,运行查看结果

可以看到,与之前没有添加移动构造函数相比,这次实验编译器使用移动构造函数替换了拷贝构造函数,并且没有对类B进行拷贝构造,类A的析构函数也少了很多,具体而言

  • 前两行的普通构造仍旧是getA函数的局部对象a引起的
  • 第三行的移动构造则是getA函数返回临时对象时引起的,因为编译器发现这是一个临时对象,而当移动构造函数存在的时候,临时对象被右值引用类型的参数所接受,也就是被移动构造函数的参数接受
  • 第四行的析构则是getA函数运行结束,其内的局部对象a被销毁引起的
  • 第五行的移动构造则是由测试函数对getA函数的调用引起的

实验二

而如果我们再将测试函数中代码稍作改变,如下,将测试函数中的a类型修改为右值引用

void test()
{
	A&& a=getA();
}

 再次运行观察实验结果

会发现编译器只进行了一次移动构造函数的调用,除此以外再没有其他多余的调用!

这是因为getA返回的临时对象引起了移动构造函数的调用,而这个临时对象返回到测试函数后被测试函数中右值引用类型的a直接接管

并且从此刻开始,这个由getA返回临时对象的生命周期将同test函数中a的声明周期一样(可以认为此时的a就是这个返回的临时对象),因此当测试函数结束时只进行了一次析构函数的调用倒数第二行和倒数第三行的析构是getA函数结束时引起的对局部对象a的销毁

实验三

接下来我们探讨在右值引用文章中提到的std::move()函数的作用

左值、右值、左值引用与右值引用-CSDN博客

在测试函数中追加一行代码

void test()
{
	A&& a=getA();
	A a1(a);
}

编译运行观察输出结果

可以看到,追加的代码引起了拷贝构造函数的调用

接下来,我们修改代码如下:

void test()
{
	A&& a=getA();
	A a1(std::move(a));
}

再次观察结果发现,原来的拷贝构造函数的现在被移动构造函数替换了,原因就是因为std::move()函数将对象a从左值类型强制转换成了右值类型,而右值类型的变量会被具有右值引用类型形参的一点构造函数所接收 

因此,我们再次看到,所谓std::move()函数只是一个类型转换函数,其作用就是将一个左值对象强制转换为右值

如果我们继续修改代码

void test()
{
	A&& a=getA();
	A &&a1(std::move(a));
}

运行观察结果

会发现编译器只进行了一次移动构造函数的调用,但此时需要注意,这行代码根本不产生新对象,当然也不会调用类A的移动构造函数,可以通过跟踪调试观察,这行代码的效果等同于把对象a的名修改为a1,或者说对象a和对象a1代表同一个对象

移动赋值运算符

在原有的基础上对类A增加拷贝赋值运算符和移动赋值运算符

//拷贝赋值运算符
	A operator=(const A& src)
	{
		if(this==&src)
			return *this;
		delete this->m_pb;
		this->m_pb=new B(*src.m_pb);//值的赋值
		cout<<"类A的拷贝赋值运算符调用了"<<endl;
		return *this;
	}

	//移动赋值运算符
	A operator=(A&& a1)
	{
		if(this==&a1)
			return *this;
		delete this->m_pb;
		this->m_pb=a1.m_pb;//指针的接管
		a1.m_pb=nullptr;
		return *this;
	}

测试函数如下:

void test()
{
	A a=getA();
	A a2;
	a2=std::move(a);
}

合成的移动操作

如果不生成自己的拷贝构造函数和拷贝赋值运算符,那么,在某些情况下,编译器会合成拷贝构造函数和拷贝赋值运算符,同样道理,在某些情况下,编译器会合成移动构造函数和移动赋值运算符。针对合成问题有一些说法,总结如下:

  • 如果一个类定义了自己的拷贝构造函数、拷贝赋值运算符或者析构函数(这三者之一,表示程序员要自己处理对象的复制或者释放问题),编译器就不会为它合成移动构造函数和移动赋值运算符。这说明只要程序员有自己复制对象和释放对象的倾向,编译器就不会帮助程序员生成移动动作的相关函数(所以有一些类是没有移动构造函数和移动赋值运算符的),这样就可以防止编译器合成出一个完全不是程序员自己想要的移动构造函数或者移动赋值运算符。
  • 只有一个类没定义任何自己版本的拷贝构造函数、拷贝赋值运算符、析构函数,且类的每个非静态成员都可以移动时,编译器才会为该类合成移动构造函数或者移动赋值运算符。
    • 可以移动的成员有
      1. 内置类型(如整型、实型等)的成员变量可以移动
      2. 如果成员变量是一个类类型,如果这个类有对应的移动操作相关的函数,则该成员变量可以移动。

总结

  1. 在有必要的情况下,应该考虑尽量给类添加移动构造函数和移动赋值运算符,达到减少拷贝构造函数和拷贝赋值运算符调用的目的,尤其是需要频繁调用拷贝构造函数和拷贝赋值运算符的场合。
  2. 不抛出异常的移动构造函数、移动赋值运算符都应该加上noexcept,用于通知编译器该函数本身不抛出异常。否则有可能因为系统内部的一些运作机制原本程序员认为可能会调用移动构造函数的地方却调用了拷贝构造函数。此外,此举还可以提高编译器的工作效率。
  3. 一个对象移动完数据后当然不会自主销毁,但是,程序员有责任使这种数据被移走的对象处于一种可以被释放(析构)的状态
  4. 一个本该由系统调用移动构造函数和移动赋值运算符的地方,如果类中没有提供移动构造函数和移动赋值运算符,则系统会调用拷贝构造函数和拷贝赋值运算符代替。

参考:
《c++新经典》

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

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

相关文章

2023年全国职业院校技能大赛(高职组)“云计算应用”赛项赛卷④

2023年全国职业院校技能大赛&#xff08;高职组&#xff09; “云计算应用”赛项赛卷4 目录 需要竞赛软件包环境以及备赛资源可私信博主&#xff01;&#xff01;&#xff01; 2023年全国职业院校技能大赛&#xff08;高职组&#xff09; “云计算应用”赛项赛卷4 模块一 …

2022 年全国职业院校技能大赛高职组云计算赛项试卷部分解析

2022 年全国职业院校技能大赛高职组云计算赛项试卷部分解析 【赛程名称】高职组-云计算赛项第一场-私有云【任务 1】私有云服务搭建[10 分]【题目 2】Yum 源配置[0.5 分]【题目 3】配置无秘钥 ssh[0.5 分]【题目 4】基础安装[0.5 分]【题目 5】数据库安装与调优[0.5 分]【题目 …

Open CASCADE学习|模块组成

OpenCASCADE由七个模块组成&#xff0c;分别如下&#xff1a; Foundation Classes基础类 Modeling Data 建模数据 Modeling Algorithms 建模算法 Visualization 可视化 Data Exchange 数据交换 Application Framework 程序框架 Kernel Classes 核心类 2D Geometry 二维几…

JDBC初体验(一)

一、JDBC概述 JDBC&#xff08;Java DateBase Connectivity&#xff09;是Java数据库连接技术的简称&#xff0c;提供连接各种常用数据库的能力 1.1 JDBC工作原理 JDBC API 提供者&#xff1a;Sun公司 作用&#xff1a;Java访问数据库的标准规范。提供给程序员调用的接口与类…

基于ssm的图书馆书库管理系统+vue论文

摘 要 当下&#xff0c;如果还依然使用纸质文档来记录并且管理相关信息&#xff0c;可能会出现很多问题&#xff0c;比如原始文件的丢失&#xff0c;因为采用纸质文档&#xff0c;很容易受潮或者怕火&#xff0c;不容易备份&#xff0c;需要花费大量的人员和资金来管理用纸质文…

深度学习分类任务中的准确率、精确率(查准率)、召回率(查全率)、F1值、ROC曲线的AUC值,

0. 混淆矩阵 其中关于 TP, TN; FP, FN 的解释&#xff1b; 其中 首字母 T&#xff0c;F代表预测的情况&#xff0c;即T代表预测的结果是对的&#xff0c; F代表预测的结果是错误的&#xff1b; 第二个字母代表预测是预测为 正样本&#xff0c;还是负样本&#xff0c; Positve…

<软考高项备考>《论文专题 - 57 干系人管理(1) 》

1 论文基础 1.1 写作要点 过程定义、作用写作要点、思路识别干系人识别干系人是定期识别项目干系人&#xff0c;分析和记录他们的利益、参与度、相互依赖性、影响力和对项目成功的潜在影响的过程。作用:使项目团队能够建立对每个干系人或干系人群体的适度关注。本项目里有哪些…

VMware虚拟机安装Ubuntu

准备:Ubuntu的镜像文件,VMware,手. 1.新建虚拟机&#xff0c;选择自定义&#xff0c;下一步。 2.硬件兼容性&#xff0c;选择Workstation 16.x&#xff0c;下一步。 3.选择安装程序光盘映像文件&#xff0c;路径为映像文件所在文件夹&#xff0c;下一步。 4. 创建用户和设置密…

Docker部署情侣恋爱网站

个人名片&#xff1a; 对人间的热爱与歌颂&#xff0c;可抵岁月冗长&#x1f31e; 个人主页&#x1f468;&#x1f3fb;‍&#x1f4bb;&#xff1a;念舒_C.ying 个人博客&#x1f30f; &#xff1a;念舒_C.ying 情侣恋爱网站 1. 修改代码2. 目录结构3. 编写Dockerfile4. 编写d…

苹果电脑Markdown文本编辑Typora mac功能介绍

Typora mac是一款跨平台的Markdown编辑器&#xff0c;支持Windows、MacOS和Linux操作系统。它具有实时预览功能&#xff0c;能够自动将Markdown文本转换为漂亮的排版效果&#xff0c;让用户专注于写作内容而不必关心格式调整。Typora Mac版除了支持常见的Markdown语法外&#x…

企业数字化转型指南,12步实现企业转型之路

引言 在这个数字化时代&#xff0c;企业面临着前所未有的机遇和挑战。随着科技的飞速发展和市场竞争的加剧&#xff0c;传统商业模式正在经历翻天覆地的变革。数字化转型&#xff0c;已经不再只是一种选择&#xff0c;而是企业生存和发展的必然路径。它不仅仅是技术的升级&…

网络正常运行时间监控工具

正常运行时间是衡量系统可靠性的指标&#xff0c;表示为机器工作和可用时间的百分比。当提到 IT 网络时&#xff0c;正常运行时间是衡量网络设备、网站和其他服务的可用性的指标。网络正常运行时间通常以百分位数来衡量&#xff0c;例如“五个 9”&#xff0c;这意味着系统在 9…

K8S中SC、PV、PVC的理解

存储类&#xff08;StorageClass&#xff09;定义了持久卷声明&#xff08;PersistentVolumeClaim&#xff09;所需的属性和行为&#xff0c;而持久卷&#xff08;PersistentVolume&#xff09;是实际的存储资源&#xff0c;持久卷声明&#xff08;PersistentVolumeClaim&#…

基于ssm的无纸化学习平台的设计与实现论文

目 录 目 录 I 摘 要 III ABSTRACT IV 1 绪论 1 1.1 课题背景 1 1.2 研究现状 1 1.3 研究内容 2 2 系统开发环境 3 2.1 vue技术 3 2.2 JAVA技术 3 2.3 MYSQL数据库 3 2.4 B/S结构 4 2.5 SSM框架技术 4 3 系统分析 5 3.1 可行性分析 5 3.1.1 技术可行性 5 3.1.2 操作可行性 5 3…

代码随想录算法训练营第二十四天| 回溯 491.递增子序列 46.全排列 47.全排列 II

491. 非递减子序列 此前通过used数组去重的操作的前提是需要首先给数组排序&#xff0c;本题不可以&#xff0c;因为求递增子序列时&#xff0c;原先的序列并不是一定递增的&#xff0c;此时进行排序后&#xff0c;此时递增子序列会包含其他原先不是原先数据的子序列。 递归参…

【算法刷题】Day28

文章目录 1. 买卖股票的最佳时机 III题干&#xff1a;算法原理&#xff1a;1. 状态表示&#xff1a;2. 状态转移方程3. 初始化4. 填表顺序5. 返回值 代码&#xff1a; 2. Z 字形变换题干&#xff1a;算法原理&#xff1a;1. 模拟2. 找规律 代码&#xff1a; 1. 买卖股票的最佳时…

Hex2Bin转换软件、Bootloader 、OTA加密升级 、STM32程序加密、其他MCU同样适用

说明&#xff1a;这个工具可以将 Hex 文件 转换为 Bin 格式文件&#xff0c;软件是按自己开发 STM32 OAT 功能需求开发的一款辅助 上位机软件。 文中的介绍时 bootloader boot 文档在补充完善中... 有兴趣的朋友可留言探讨。 1. 软件功能&#xff1a; 1.生成 bin&#x…

IntelliJ IDEA如何使用固定地址公网远程访问本地Mysql数据库

文章目录 1. 本地连接测试2. Windows安装Cpolar3. 配置Mysql公网地址4. IDEA远程连接Mysql小结 5. 固定连接公网地址6. 固定地址连接测试 IDEA作为Java开发最主力的工具&#xff0c;在开发过程中需要经常用到数据库&#xff0c;如Mysql数据库&#xff0c;但是在IDEA中只能连接本…

SSM+mysql外卖APP-计算机毕业设计源码04871

摘要 立足于当下餐饮行业现有的点餐模式&#xff0c;分析传统APP点餐的运作流程&#xff0c;结合Android系统的特点设计新型的外卖APP。近几年&#xff0c;人们生活水平日益提升&#xff0c;但工作强度和压力不断增强&#xff0c;尤其是对于上班族而言&#xff0c;到餐厅吃饭费…

面试宝典进阶之关系型数据库面试题

D1、【初级】你都使用过哪些数据库&#xff1f; &#xff08;1&#xff09;MySQL&#xff1a;开源数据库&#xff0c;被Oracle公司收购 &#xff08;2&#xff09;Oracle&#xff1a;Oracle公司 &#xff08;3&#xff09;SQL Server&#xff1a;微软公司 &#xff08;4&#…