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

对象移动

对象移动,就是把一个不想用了的对象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.mzph.cn/news/610685.shtml

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

相关文章

Java中什么是多线程?

Java是一种支持多线程编程的编程语言&#xff0c;它提供了内置的多线程支持&#xff0c;使得开发者能够创建并发执行的程序。多线程是一种在同一程序中同时执行多个线程的机制&#xff0c;每个线程都是独立运行的&#xff0c;并且可以共享相同的资源。在Java中&#xff0c;多线…

设计模式之观察者模式【行为型模式】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档> 学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某…

把图表题注编号由“0.1”改为“1.1“ (方法二)

前置设置&#xff1a; 手打章节标题&#xff0c;“第一章 绪论”&#xff0c;“第二章 相关理论和技术方法”。给章节标题设置样式 “标题一”&#xff0c;设置为一级标题。打开导航窗格&#xff0c;可以不开&#xff0c;我纯粹是为了操作方便。直接先给图片插入题注&#xff…

cpolar 内网穿透 使用

cpolar 内网穿透 使用 官网地址&#xff1a;https://www.cpolar.com/ 官网文档&#xff1a;https://www.cpolar.com/blog/cpolar-quick-start-tutorial-centos-series 获取隧道Authtoken&#xff1a;https://dashboard.cpolar.com/auth 步骤 1、先去注册 在这个地方注册&…

重磅2023年度openGauss标杆应用实践案例正式揭晓

12月28日&#xff0c;在openGauss Summit 2023峰会上&#xff0c;正式揭晓了“2023年度openGauss标杆应用实践案例”。 数据库作为企业IT系统的核心组成部分&#xff0c;是数字基础设施建设的关键&#xff0c;也是实现数据安全稳定的保障。为更好地推动产业技术创新&#xff0…

使用Linux防火墙管理HTTP流量

在Linux系统中&#xff0c;防火墙是用于控制网络流量的重要工具。通过防火墙&#xff0c;你可以根据需要限制、过滤或允许特定的网络流量&#xff0c;从而提高系统的安全性。在处理HTTP流量时&#xff0c;防火墙可以帮助你实施访问控制、流量监控和其他安全策略。 iptables i…

PPT插件-大珩助手-免费功能-特殊格式介绍

上、下标切换 直接切换选中的字符为上、下标。 大小金额 支持超大金额的大写金额转换 当前日期 本次打开文件的时间 转二维码 将当前选中的文字&#xff0c;转为二维码图片&#xff0c;并插入到PPT当前位置 特殊字符 内置常用的特殊字符&#xff0c;点击使用 软件介绍 …

Java多线程并发篇----第五篇

系列文章目录 文章目录 系列文章目录前言一、Java中interrupted 和 isInterruptedd方法的区别?二、Java中synchronized 和 ReentrantLock 有什么不同?三、有三个线程T1,T2,T3,如何保证顺序执行?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分…

关于CodeReview的一些实践和思考

在日常开发中&#xff0c;Code Review 的重要性日益凸显。它不仅有助于提升代码质量&#xff0c;还促进了团队成员之间的知识共享和技能提升。本文将主要聚焦于 Code Review&#xff0c;分享在这个过程中的一些心得和思考。 CodeReview常用到的一些术语 之前看到公司的大佬经…

autoxjs 安卓爬虫自动化

autoxjs 安卓爬虫自动化 我这里只是测试请勿用于违法的 我这里是小红书 文章目录 autoxjs 安卓爬虫自动化前言一、自动刷直播间并且抓取商品已经粉丝数量等&#xff1f;总结 前言 欢迎来到AutoXJS的世界&#xff0c;这是一个充满创新、挑战和技术探索的领域。在这个引领未来的…

基于Springboot+vue高校宿舍管理系统(前后端分离)

该项目完全免费 高校宿舍管理系统采用前后端分离的架构方式&#xff0c;是为学校宿舍管理打造的一套系统,可以让管理者更为便捷地处理学生公寓问题,从而大大提高管理效率,让学生公寓的资源合理分配,事半功倍,进而改善了学生公寓管理。 系统分为三种角色&#xff0c;分别是系统…

小巧且兼具高性能的小模型 TinyLlama 等

TinyLlama-1.1B 小模型在边缘设备上有着广泛的应用&#xff0c;如智能手机、物联网设备和嵌入式系统&#xff0c;这些边缘设备通常具有有限的计算能力和存储空间&#xff0c;它们无法有效地运行大型语言模型。因此&#xff0c;深入探究小型模型显得尤为重要。 来自新加坡科技…

[ 机器学习 ] 关于Jupyter Notebook中pytorch模块import失败的问题

0x01、问题描述 在使用WSL搭建Jupyter进行代码测试的时候 发现Miniconda&#xff08;虚拟环境均适用&#xff09;中安装的pytorch在Jupyter里面import失败 但在python解释器的命令模式里可以测试import成功 并且torch.cuda_available()打印True 以前用的是IDEA没怎么用Jup…

Navicat迁移局域网内其他PC机的MySQL数据库

迁移局域网内其他PC机的MySQL数据库到本机 查看局域网IP 设置可远程连接的账号 开放本机防火墙的3306端口 连接PC机的MySQL 利用Navicat迁移数据库 刚换了个电脑&#xff0c;旧电脑的MySQL数据库太多了&#xff0c;转成.sql文件&#xff0c;再传输到新电脑上运行&#xff…

CSS3新增边框样式

边框样式 概念:在CSS3中&#xff0c;针对元素边框增加了丰富的修饰属性。 常见的边框样式属性有以下 属性说明border-radius圆角效果box-shadow边框阴影border-image边框背景 border-radius属性 概念&#xff1a;border-radius属性可以为元素添加圆角效果 语法&#xff1…

盘点五大设备巡检系统!内附巡检注意事项及巡检要点

在繁忙的工业生产现场&#xff0c;设备高效运行是保证生产顺利开展的关键。为了确保设备的稳定性和生产的安全性&#xff0c;设备巡检工作变得尤为重要。 举个通俗易懂的例子—— 你开了一家钢铁制造厂&#xff0c;工厂拥有多条自动化生产线&#xff0c;每天24小时不间断地运…

运用tomcat在浏览器中对数据库信息进行查询

在idea中创建好项目后&#xff0c;添加web项目 然后打开idea的setting&#xff0c;跳转到下面的页面&#xff0c;下载maven插件。 出现下面的选项&#xff0c;才正确。 添加好web项目后&#xff0c;打开pom文件&#xff0c;添加相应的依赖&#xff1a; <?xml version"…

golang中的循环依赖

作为 Golang 开发人员&#xff0c;您可能遇到过导入周期。Golang 不允许导入循环。如果 Go 检测到代码中的导入循环&#xff0c;则会抛出编译时错误。在这篇文章中&#xff0c;让我们了解导入周期是如何发生的以及如何处理它们。 导入周期 假设我们有两个包&#xff0c;p1并且…

【mysql】—— 用户管理

目录 &#xff08;一&#xff09;为什么要有用户管理&#xff1f; &#xff08;二&#xff09;用户 2.1 查看用户信息 2.2 创建用户 2.3 删除用户 2.4 修改用户密码 &#xff08;三&#xff09;数据库的权限 3.1 给用户授权 3.2 回收权限 &#xff08;一&#xff09;为…

互联网加竞赛 基于大数据的社交平台数据爬虫舆情分析可视化系统

文章目录 0 前言1 课题背景2 实现效果**实现功能****可视化统计****web模块界面展示**3 LDA模型 4 情感分析方法**预处理**特征提取特征选择分类器选择实验 5 部分核心代码6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于大数据…