c++临时对象的探讨及相关性能提升

产生临时对象的情况

我们定义一个类进行测试


class tempVal {
public:
	int v1, v2;
	tempVal(int v1 = 0, int v2 = 0);
	tempVal(const tempVal& t) :v1(t.v1), v2(t.v2) 
	{
		cout << "调用拷贝构造函数" << endl;
		
	}
	virtual ~tempVal() 
	{
		cout << "调用析构函数" << endl;
	}
};

tempVal::tempVal(int v1, int v2) :v1(v1), v2(v2) 
{
	cout << "调用了构造函数" << endl;
	cout << "v1:" << v1 << endl;
	cout << "v2:" << v2 << endl;
}

以值传递的方式给函数传递参数

在文件中添加以下函数,其中函数的参数是我们定义的tempVal类,且使用的是值传递方式传参

int add(tempVal t)
{
	int tmp = t.v1 + t.v2;
	t.v1 = 1000;
	return tmp;
}

在main函数中添加代码

int main()
{
	tempVal tm(10, 20);
	int sum = add(tm);
	cout << "sum=" << sum << endl;
	cout << tm.v1 << endl;
    system("pause");
	return 0;
}

运行结果如下:

 观察上述输出结果,可以看到代码中输出了拷贝构造函数,这是因为调用Add成员函数时把对象tm传递给了Add函数,此时,系统会调用拷贝构造函数创建一个副本t(成员函数Add的形参),把tm对象复制给形参t,因为这是一个副本(复制),所以可以注意到,修改副本的val1的值为1000,并不会影响到外界tm对象的val1值(tm对象的val1值仍旧为10)

代码行中的形参t是一个局部对象(局部变量),从程序功能的角度来讲,函数体内需要临时使用它一下,来完成求和运算,严格意义上来讲,它不能称为一个临时对象,因为真正的临时对象往往指的是真实存在,但又感觉不到的对象(至少从代码上是不能直接看到的对象)。

但是代码生成了t对象,调用了tempVal类的拷贝构造函数,有了复制的动作,就会影响程序执行效率。修改代码以提升性能的方式也很简单,把传参方式修改为引用传参即可,即:

int add(tempVal& t)

再次运行观察结果:

观察上面的结果可以发现,少了一次调用拷贝构造函数和析构函数,提升了效率,如果对象很大,并且还从其他父类继承(继承会导致父类的拷贝构造函数也执行),那效率也许会提升很大,但是tm.value1的值在函数内部修改,直接被带到了函数外部,影响了函数外部tm对象的值,这就是引用的能力 

类型转换生成的临时对象

现在修改main函数内的代码:

	tempVal t1;
	t1=1000;

运行观察结果

观察上述结果发现,系统调用了两次构造函数,其中第一次是声明对象t1时调用的构造函数,而第二次构造函数的调用则是因为类型转换生成临时对象造成的

具体而言,系统会在此时生成一个临时对象(我们无法知道这个临时对象的名字和地址),之后调用构造函数把1000赋给这个临时对象的v1,而v2使用默认参数进行初始化再把这个临时对象赋值给t1,之后再调用析构函数是否掉生成的临时对象 (注意析构函数的调用销毁的是生成的临时对象,而不是t1,因为代码system("pause")的作用,此时对象t1还没有离开main函数的作用域)

为了观察方便,我们在tempVal的类中添加一个拷贝赋值运算符,如下:

tempVal& tempVal::operator=(const tempVal& tmp)
{
	cout<<"调用了拷贝赋值运算符"<<endl;
	this->v1=tmp.v1;
	this->v2=tmp.v2;
	return *this;
}

再次运行代码,观察运行结果:

现在,总结一下以上代码的运行过程:

  • 调用构造函数构造对象t1
  • 调用构造函数,将1000作为参数传递给构造函数,生成一个临时对象
  • 调用拷贝赋值运算符,将生成的临时对象赋值给对象t1
  • 是否掉临时对象

上述代码的优化也很简单,只需要将以上代码合并一下即可,如下:

tempVal t1=1000;

 此时,代码便少调用了一次构造函数、一次拷贝赋值运算符和一次析构函数

注意,针对“tempVal=1000;这行代码,这里的“=”不是赋值运算符,而是“定义时初始化”的概念。可以这样理解:在这里定义了t1对象,系统就为t1对象创建了预留空间,然后用1000调用构造函数来构造临时对象的时候,这种构造是在为t1对象创建的预留空间里进行的,所以并没有真的产生临时对象。

如果不想要隐式类型转换,将构造函数声明为explicit即可

再次观察一个由于隐式类型转换而造成生成不必要的临时对象的例子

我们在文件中定义一个新的函数,注意函数中参数的类型

void calc(const string& str)
{
	const char* p=str.c_str();
	return;
}

接下来,我们在main函数中调用这个函数,注意传给calc函数参数的类型

	char mystr[100]="I love China";
	calc(mystr);

运行代码,我们可以看到代码会正常运行成功。

但是我们看到,calc函数的形参类型为string,而我们传进去的参数类型为char数组,显然编译器为我们做了隐式类型转换,解决了代码运行过程中类型不匹配的问题,那么编译器是如何做的呢?

事实上,编译器首先调用string的构造函数生成了一个string类型的临时对象(通过我们的char数组实参mtstr进行初始化)之后形参str通过引用绑定到这个临时对象上,等函数调用结束之后,这个临时对象就被销毁了

显然,尽管我们在函数中对形参使用了左值引用,但是由于类型隐式转换的原因,我们仍旧在调用函数的过程中生成了不必要的临时对象,浪费了程序性能。

另外需要注意的是,假如我们将上述calc函数的形参中的const去掉,再次运行程序时就会报错。

造成代码报错的原因是,编译器在类型转换的过程中生成了临时对象,而形参str绑定在这个临时对象上,但是我们现在没有对形参str加const限制, 因此编译器就会认为我们有可能对str做出修改,而str绑定在临时对象上,就等于编译器认为我们可能对一个临时对象做修改,这是不被允许的。

因此,C++只会为const引用,而不会为非const引用产生临时对象

函数返回对象生成的临时对象

继续以本文最开始定义的tempVal类来说明问题

重新定义一个函数如下:

tempVal cal(tempVal& tmp)
{
	tempVal t;
	t.v1=tmp.v1*2;
	t.v2=tmp.v2*2;
	return t;
}

在main函数中调用该函数

int main()
{
	tempVal t1(10,20);
	cal(t1);
    system("pause");
	return 0;
}

 使用g++重新编译代码,并启用关闭编译优化选项

g++ main.cpp -o main.exe -fno-elide-constructors

运行可执行文件,观察终端输出结果

发现终端多输出了一次拷贝构造函数和两次析构函数的调用这是因为

  • cal函数调用结束时返回局部变量t的时候,会生成一个临时对象,把t的值拷贝给这个临时对象
  • 这个临时对象就可以在main函数中获取,从而获取到返回的值
  • 第一次析构函数的调用是cal函数结束时释放掉函数中的临时变量t
  • 第二次的析构函数的调用是main函数中(代码行55)调用cal函数结束后,返回的临时对象被释放掉了

注意,现代编译器会自动对返回值做优化,因此需要关闭优化选项才能看到返回的临时对象的生成

重新修改main函数的代码

int main()
{
	tempVal t1(10,20);
	tempVal t3=cal(t1);
    system("pause");
	return 0;
}

 再次运行观察结果

与上次相比,这次调用了两次拷贝构造函数,分析可知

  • 第一次是cal返回时产生临时对象调用的,作用是将cal函数中计算得到的变量t的值拷贝给这个临时对象返回出去
  • 第二次拷贝构造函数的调用则是将这个临时对象拷贝给t3从而构造对象t3时调用的 

右值引用在临时对象中的作用

我们继续修改代码以说明问题,将main函数的代码修改如下,也就是将t3修改为右值引用

int main()
{
	tempVal t1(10,20);
	tempVal&& t3=cal(t1);
    system("pause");
	return 0;
}

 观察运行结果发现,这次函数又只进行了一个拷贝构造函数的调用,并且少调用了一次析构函数

  • 显然该拷贝构造函数是由于返回值的临时对象产生的,但是在将这个临时对象赋值给t3的时候却没有调用拷贝构造函数
  • 这是因为使用了右值引用,将t3绑定到了cal函数返回的临时对象身上,从而避免了一次拷贝构造函数的调用
  • 并且从t3开始绑定这个返回的临时对象开始,它的生存周期将与t3的生存周期一样,这就是为什么与上次实验相比少了一次析构函数调用的原因

至此,我们已经通过代码可以“看到”右值引用的作用了

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

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

相关文章

第九届云计算与大数据分析国际会议(ICCCBDA 2024)即将召开!

​ 第九届云计算与大数据分析国际会议&#xff08;ICCCBDA 2024&#xff09;将于2024年4月25-27日在中国成都召开。ICCCBDA自创办以来&#xff0c;已经成功召开了八届。此次会议将介绍一些当前和未来的前沿技术趋势、创新方案、研究成果&#xff0c;以及和云计算和大数据分析相…

数据讲述中国故事!和鲸助力中国综合社会调查(CGSS)数据分析与可视化大赛圆满收官

中国综合社会调查&#xff08;Chinese General Social Survey&#xff0c;CGSS&#xff09;始于 2003 年&#xff0c;由中国人民大学中国调查与数据中心&#xff08;NSRC&#xff09;常年负责其相关实施工作&#xff0c;作为我国最早具全国性、综合性、连续性的学术社会调查项目…

http跟https有什么区别?

HTTPS和HTTP的概念&#xff1a; HTTP&#xff1a;是互联网上应用最为广泛的一种网络协议&#xff0c;是一个客户端和服务器端请求和应答的标准&#xff08;TCP&#xff09;&#xff0c;用于从WWW服务器传输超文本到本地浏览器的传输协议&#xff0c;它可以使浏览器更加高效&am…

探索前端跨组件通信:EventBus在Vue和React中的应用

本文作者系360奇舞团前端开发工程师 EventBus 简介 事件总线&#xff08;Event Bus&#xff09;是一种用于组件间通信的模式&#xff0c;通常用于解决组件之间的解耦和简化通信的问题。在前端框架中&#xff0c;如 Vue.js&#xff0c;事件总线是一个常见的概念。基本上&#xf…

【hcie-cloud】【21】容器详解【容器网络说明、容器存储说明、容器镜像说明、dockerfile详述、缩略词】【下】

文章目录 容器介绍&#xff0c;容器工作机制、容器常用命令说明容器网络容器网络简介容器常用网络类型 - Bridge容器常用网络类型 - Host容器常用网络类型 - None其他容器网络类型【Macvlan、Overlay、IPvlan】容器网络相关配置 容器存储容器中应用数据的存储容器持久化存储配置…

HTML5 article标签,<time>...</time>标签和pubdate属性的运用

1、<article>...</article>标签的运用 article标签代表文档、页面或应用程序中独立的、完整的、可以独自被外部引用的内容。它可以是一篇博客或报竟杂志中的文章、一篇论坛帖子、一段用户评论或一个独立的插件&#xff0c;或者其他任何独立的内容。把文章正文放在h…

【HarmonyOS4.0】第四篇-ArkUI基础实战

一、ArkUI框架简介 ArkUI开发框架是方舟开发框架的简称&#xff0c;它是一套构建 HarmonyOS / OpenHarmony 应用界面的声明式UI开发框架&#xff0c;它使用极简的UI信息语法、丰富的UI组件以及实时界面语言工具&#xff0c;帮助开发者提升应用界面开发效率 30%&#xff0c;开发…

(23)Linux的软硬连接

前言&#xff1a;上一章我们讲解了 inode&#xff0c;为文件系统收了尾&#xff0c;这几章我们充分地讲解完了文件系统的知识点&#xff0c;现在我们开始开始学习软硬链接了。 软硬链接 1、Linux 下的快捷方式&#xff1a;软链接 上一章我们介绍完了 inode &#xff0c;我们…

试用统信服务器操作系统UOS 20

作者&#xff1a;田逸&#xff08;formyz&#xff09; 试用统信Linux操作系统UOS&#xff0c;想了解一下用已有的Linux经验能否轻松驾驭它。以便在某些场景下&#xff0c;可以多一种选择。本次试验在Proxmox VE 8&#xff08;以下简称PVE 8&#xff09;平台下进行&#xff0c;采…

【题解】—— LeetCode一周小结

1.经营摩天轮的最大利润 题目链接&#xff1a; 1599. 经营摩天轮的最大利润 你正在经营一座摩天轮&#xff0c;该摩天轮共有 4 个座舱 &#xff0c;每个座舱 最多可以容纳 4 位游客 。你可以 逆时针 轮转座舱&#xff0c;但每次轮转都需要支付一定的运行成本 runningCost 。摩…

JRT打印元素绘制协议之-A4Double

以前打印相信很多人因为A4打印两个报告头大过&#xff0c;M要把一堆报告既有A4的也有A5的还有微生物的&#xff0c;可能输出Page还不那么严谨。要么换页不对叠加了、要么多空白页、中间夹杂A4报告就更加头大。也有的人为了打印页码的共几页而头大。 借助新设计的优势&#xff…

uni-app中轮播图实现大图预览

参考效果 当轮播图滑动切换的时候更新自定义下标&#xff0c;当图片被点击的时候大图预览。 参考代码 商品详情页轮播图交互 <script setup lang"ts"> // 轮播图变化时 const currentIndex ref(0) const onChange: UniHelper.SwiperOnChange (ev) > …

TAX税类小知识

历史及发展 我国作为流转税为主国家&#xff0c;目前经历3类税制&#xff1a;统收统付--包干制---分税制&#xff0c; 分别对应时间为建国--改革开放--93年朱提出分税延续至今&#xff1b; 未来趋势必然是完善所得税。 ps:流转税是间接税&#xff0c;间接转嫁给消费者&#…

TypeScript基础

ts学习 目录概述需求&#xff1a; 设计思路实现思路分析1.TypeScript 基础类型2.TypeScript 变量声明3.TypeScript 接口4.TypeScript 类5.TypeScript 函数5.TypeScript 泛型5.TypeScript 枚举TypeScript 类型推论TypeScript 类型兼容性TypeScript 高级类型TypeScript 迭代器和生…

深度解析Dubbo的基本应用与高级应用:负载均衡、服务超时、集群容错、服务降级、本地存根、本地伪装、参数回调等关键技术详解

负载均衡 官网地址&#xff1a; http://dubbo.apache.org/zh/docs/v2.7/user/examples/loadbalance/ 如果在消费端和服务端都配置了负载均衡策略&#xff0c; 以消费端为准。 这其中比较难理解的就是最少活跃调用数是如何进行统计的&#xff1f; 讲道理&#xff0c; 最少活跃数…

金和OA C6 upload_json 任意文件上传漏洞

产品介绍 金和网络是专业信息化服务商,为城市监管部门提供了互联网监管解决方案,为企事业单位提供组织协同OA系统开发平台,电子政务一体化平台,智慧电商平台等服务。 漏洞概述 金和 OA C6 upload_json接口处存在任意文件上传漏洞&#xff0c;攻击者可以通过构造特殊请求包上…

视频号小店新手该怎么运营?团队实操经验分享!

我是电商珠珠 视频号小店作为一个新兴平台&#xff0c;自然预示着有更多的机会在。 所以有很多新手想要了解这个平台并进行入驻&#xff0c;但是却不知道该怎么去运营&#xff0c;没有货源怎么办。 今天我就来给大家详细的讲一下。 一、入驻 入驻的话需要一张企业的营业执…

阿里巴巴秋招前端笔试题

单选题 下面的 JSX 代码中&#xff0c;哪一个无法达到预期的效果&#xff1f; A.<h2>Hello World</h2> B.<input type”checkbox”/> C.<div class”msg-box”>{msg}</div> D.<label htmlFor”name”>Leo</label> E.div styl…

人工智能_机器学习091_使用三维瑞士卷数据_KMeans聚类算法进行瑞士卷数据聚类---人工智能工作笔记0131

然后我们首先来构建一下数据 准备瑞士卷数据: import numpy as np 导入数学计算包 import matplotlib.pyplot as plt 导入画图包 #自底向上聚类 from sklearn.cluster import AgglceerativeClustering 导入分层聚类模型 from sklearn.datasets import make_swiss_roll # 瑞士卷…

“五岳杯”2023量子计算挑战赛启动仪式在南方科技大学成功举办

​2023年11月4日下午&#xff0c;由北京图象图形学学会、中国移动云能力中心&#xff08;以下简称“移动云”&#xff09;主办&#xff0c;南方科技大学承办&#xff0c;北京玻色量子科技有限公司&#xff08;以下简称“玻色量子”&#xff09;协办的“五岳杯”2023量子计算挑战…