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的生存周期一样,这就是为什么与上次实验相比少了一次析构函数调用的原因

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

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

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

相关文章

【python】——turtle动态画

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…

AR HUD全面「上新」

AR HUD赛道正在迎来新的时代。 上周&#xff0c;蔚来ET9正式发布亮相&#xff0c;新车定位为D级行政旗舰轿车&#xff0c;其中&#xff0c;在智能座舱交互层面&#xff0c;继理想L系列、长安深蓝S7之后&#xff0c;也首次取消仪表盘&#xff0c;取而代之的是业内首个全焦段AR H…

分块矩阵的定义、计算

目录 一、定义 二、分块矩阵的加减乘法 三、考点 一、定义 分块&#xff0c;顾名思义&#xff0c;将整个矩阵分成几部分&#xff0c;如下图所示 二、分块矩阵的加减乘法 三、考点 分块矩阵的考点不多&#xff0c;一般来说&#xff0c;有一种&#xff1a; 求分块矩阵的转置…

PHP如何拆分中文名字(包括少数民族名字)

/*** param string|null $name* return array|null*/ function splitName($name) {if (empty($name) || empty(trim($name))) {return null;}//该正则是用来提取$name参数里面的中文字符的。preg_match_all(/[\x{4e00}-\x{9fff}]/u, $name, $matchers);$matchersCount isset($…

2024年,谷歌云首席技术官眼中的生成AI三大支柱,来看看有啥新花样

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

App在线封装的革命性创新

随着移动互联网的蓬勃发展&#xff0c;App已经成为我们日常生活中不可或缺的一部分。从购物、交通、社交到娱乐&#xff0c;几乎每个人的智能手机都装载着数十个应用程序&#xff0c;以满足各式各样的需求。然而&#xff0c;对于许多非技术背景的企业家和小型企业而言&#xff…

【机器学习】模型参数优化工具:Optuna使用分步指南(附XGB/LGBM调优代码)

常用的调参方式和工具包 常用的调参方式包括网格搜索(Grid Search)、**随机搜索(Random Search)和贝叶斯优化(Bayesian Optimization)**等。 工具包方面&#xff0c;Scikit-learn提供了GridSearchCV和RandomizedSearchCV等用于网格搜索和随机搜索的工具。另外&#xff0c;有一…

VS报错:error:LNK2005 _main 已经在 *.obj 中定义

应该是重定义了&#xff0c;但是又解决不了&#xff0c;看似又没有重定义啊&#xff0c;就在一个文件定义了啊&#xff1f;怎么会出现这种情况呢&#xff1f;关键是&#xff0c;编译报错&#xff0c;程序运行不了了。 这里提一下我的前期操作&#xff0c;是因为将一个头文件和…

NULL是什么?

NULL是一个编程术语&#xff0c;通常用于表示一个空值或无效值。在很多编程语言中&#xff0c;NULL用于表示一个变量或指针不引用任何有效的对象或内存位置。 NULL可以看作是一个特殊的值&#xff0c;表示缺少有效的数据或引用。当一个变量被赋予NULL值时&#xff0c;它表示该变…

【面试宝典】图解ARP协议、TCP协议、UDP协议

一、ARP协议 二、TCP协议 三、UDP协议 四、TCP和UDP的区别

如何有效使用360评估

导语&#xff1a;360度评估是绩效考核方法之一&#xff0c;被评估者不仅可以从自己、上司、部属、甚至顾客处获得多种角度的反馈&#xff0c;也可从这些不同的反馈清楚地认识到自己的不足、长处与发展需求。但360度评估也有其适用的范围和条件&#xff0c;华为总裁任正非给出了…

impala元数据自动刷新

一.操作步骤 进入CM界面 > Hive > 配置 > 搜索 启用数据库中的存储通知(英文界面搜索&#xff1a;Enable Stored Notifications in Database)&#xff0c;并且勾选&#xff0c;注意一定要勾选&#xff0c;配置后面的配置不生效。数据库通知的保留时间默认为2天&#…

Nacos下载与安装【windows】

&#x1f95a;今日鸡汤&#x1f95a; 我不知将去何方&#xff0c;但我已经在路上。 ——宫崎骏《千与千寻》 目录 &#x1f95e;1.Nacosdi地址 &#x1f32d;2.GitHub下载 &#x1f37f;3.目录结构 &#x1f953;4.启动nacos &#x1f9c2;5.客户端登陆 &#x1f9c8…

GitHub 一周热点汇总第5期(2024/01/07-01/13)

GitHub一周热点汇总第5期 (2024/01/07-01/13)&#xff0c;梳理每周热门的GitHub项目&#xff0c;这一周的热门项目都普遍比较年轻&#xff0c;有几个更是刚刚发布就火热起来了&#xff0c;一起来看看都有哪些项目吧。 #1 llm-course 项目名称&#xff1a;llm-course - 大语言…

华为路由器OSPF动态链路路由协议配置

R1配置 interface GigabitEthernet0/0/0ip address 10.1.12.1 255.255.255.252 interface LoopBack0ip address 1.1.1.1 255.255.255.255 ospf 1 router-id 1.1.1.1 area 0.0.0.0 network 1.1.1.1 0.0.0.0 network 10.1.12.0 0.0.0.3 R2配置 interface GigabitEthernet0/0/0i…

特征工程-特征处理(一)

特征处理-&#xff08;离散型特征处理&#xff09; 完成特征理解和特征清洗之后&#xff0c;我们要进行特征工程中最为重要和复杂的一步了——特征处理 离散型特征处理 离散型特征通常为非连续值或以字符串形式存在的特征&#xff0c;离散型特征通常来讲是不能直接喂入模型中…

四川云汇优想教育咨询有限公司引领电商未来

四川云汇优想教育咨询有限公司&#xff0c;一家在电商服务领域崭露头角的领军企业&#xff0c;致力于为广大客户提供最优质、最全面的电商服务。作为业界翘楚&#xff0c;云汇优想凭借其卓越的服务品质和强大的技术实力&#xff0c;在激烈的市场竞争中独树一帜&#xff0c;赢得…

将WAP网站封装成App体验的全新策略

一、传统的App封装方式 传统的App封装技术通常依赖于WebView组件&#xff0c;将WAP内容嵌入到一个原生App框架中。这种方法虽然可以快速实现WAP到App的转换&#xff0c;但存在着明显的缺陷&#xff1a;首先&#xff0c;WebView的性能和用户体验都无法与原生组件相提并论&#x…

金蝶云星空与金蝶云星空对接集成逐个单据查询连通员工新增(3009-销售退货清理卡片)

金蝶云星空与金蝶云星空对接集成逐个单据查询连通员工新增(3009-销售退货清理卡片) 来源系统:金蝶云星空 金蝶K/3Cloud&#xff08;金蝶云星空&#xff09;是移动互联网时代的新型ERP&#xff0c;是基于WEB2.0与云技术的新时代企业管理服务平台。金蝶K/3Cloud围绕着“生态、人人…

【TC3xx芯片】TC3xx芯片电源管理系统PMS详解

目录 前言 正文 1.供电模式选择&#xff08;Supply Mode Selection&#xff09; 1.1 供电域 1.2 供电模式 1.3 供电阈值 1.4 供电上升和下降行为Supply Ramp-up and Ramp-down Behavior 1.5 EVRC产生供电 2. 电源监控 2.1 电源监控原理 2.2 Primary低电压监控 2.3 …