对象被优化以后才是高效的C++编程


课程总目录


文章目录

  • 一、对象会调用哪些方法、对象优化的三个原则
  • 二、CMyString的代码问题
  • 三、
  • 四、添加带右值引用参数的拷贝构造和赋值函数
  • 五、CMyString在vector上的应用
  • 六、move移动语义和forward类型完美转发
  • 七、再聊vector容器使用对象过程中的优化


一、对象会调用哪些方法、对象优化的三个原则

代码示例1:

有一个Test类:

class Test
{
public:Test(int a = 10) :ma(a) { cout << "Test(int)" << endl; }~Test() { cout << "~Test()" << endl; }Test(const Test& t) :ma(t.ma) { cout << "Test(const Test&)" << endl; }Test& operator=(const Test& t){cout << "operator=" << endl;ma = t.ma;return *this;}
private:int ma;
};
Test t1;
Test t2(t1);
Test t3 = t1;	// 定义的时候,这也是拷贝构造,不要被迷惑// 运行结果:
Test(int)
Test(const Test&)
Test(const Test&)
~Test()
~Test()
~Test()
// 显式生成临时对象,生存周期:所在的语句
// 语句结束,临时对象会析构
Test t4 = Test(20);
cout << "-----------------------" << endl;// 运行结果:
Test(int)
-----------------------
~Test()

这里的运行结果很奇怪,很多人可能认为Test t4=Test(20);是临时对象先构造,然后拿临时对象拷贝构造t4,然后语句结束,临时对象析构,那为什么这里析构是在横线之后??

这就是因为c++编译器对于对象的构造的优化:用临时对象生成新对象的时候,临时对象就不产生了,相当于直接构造新对象

也就是Test t4 = Test(20);等价于Test t4(20);,所以此时对象的析构析构是在对象生命周期结束时(main函数结束处)

再来看一些语句:

t4 = t2; // 这调用的是t4.operator=(t2)赋值函数,因为t4原本已存在
t4 = Test(30);
/*
Test(30)显式生成临时对象
t4原本已存在,所以不是构造,这个临时对象肯定要构造生成的 
临时对象生成后,给t4赋值,t4.operator=(const Test &t)
出语句后,临时对象析构 
*/t4 = (Test)30; 
/*
这里是把30强转成Test类型 
把其他类型转成类类型的时候,编译器就看这个类类型有没有合适的构造函数
现在把int转成Test,就看这个类类型有没有带int类型参数的构造函数 int->Test(int)
有的话,就可以显式生成临时对象Test(30)赋值给t4,出语句后,临时对象析构 
*/// 隐式生成临时对象Test(30),效果同上,这仨都是一样的效果
t4 = 30; // int->Test(int)
Test* p = &Test(40);
/*
生成一个临时对象,出语句后,临时对象析构了 
此时p指向的是一个已经析构的临时对象,p相当于野指针了,这是不行的
而且,这段代码也会报错:“&"要求左值
*/const Test& ref = Test(50);
/*
生成一个临时对象,但是此时是常量左值引用
那么此时	出语句后,临时对象不析构了,因为引用相当于是别名,这块内存起了个名字
所以,引用变量引用临时对象是安全的,临时对象的生命周期就变成引用变量的生命周期了
现在,引用变量是这个函数的局部变量,main函数结束,这个临时对象才析构
*/

代码示例2:

class Test
{
public:// 这里有默认值,Test() Test(10) Test(10, 10)这三种都行Test(int a = 5, int b = 5):ma(a), mb(b){cout << "Test(int, int)" << endl;}~Test() { cout << "~Test()" << endl; }Test(const Test& src):ma(src.ma), mb(src.mb){cout << "Test(const Test&)" << endl;}void operator=(const Test& src){ma = src.ma;mb = src.mb;cout << "operator=" << endl;}
private:int ma;int mb;
};Test t1(10, 10);						// 1.Test(int, int)
int main()
{Test t2(20, 20);					// 3.Test(int, int)Test t3 = t2;						// 4.Test(const Test&)// 优化了,相当于:static Test t4(30, 30);static Test t4 = Test(30, 30);		// 5.Test(int, int)// 显式生成临时对象t2 = Test(40, 40);					// 6.Test(int, int)  operator=  ~Test()// 显式生成临时对象// (20, 50)括号表达式,值是50// 也即(Test)50,去找Test(int)一个参数的构造t2 = (Test)(20, 50);				// 7.Test(int, int)  operator=  ~Test()// 隐式生成临时对象// 去找Test(int)一个参数的构造t2 = 60;							// 8.Test(int, int)  operator=  ~Test()// new出来的要delete才析构Test* p1 = new Test(70, 70);		// 9.Test(int, int)Test* p2 = new Test[2];				// 10.Test(int, int) Test(int, int)Test* p3 = &Test(80, 80);			// 11.Test(int, int)  ~Test(),报错,别这样用const Test& p4 = Test(90, 90);		// 12.Test(int, int)delete p1;							// 13.~Test()delete[]p2;							// 14.~Test()  ~Test()
}
Test t5(100, 100);						// 2.Test(int, int)

代码示例3:

class Test
{
public:Test(int data = 10) :ma(data) { cout << "Test(int)" << endl; }~Test() { cout << "~Test()" << endl; }Test(const Test& t) :ma(t.ma) { cout << "Test(const Test&)" << endl; }void operator=(const Test& t){cout << "operator=" << endl;ma = t.ma;}int getData()const { return ma; }private:int ma;
};Test GetObject(Test t)
{int val = t.getData();Test tmp(val);return tmp;
}int main()
{Test t1;Test t2;t2 = GetObject(t1);return 0;
}

理论运行结果:

Test(int)			// 1
Test(int)			// 2
Test(const Test&)	// 3
Test(int)			// 4
Test(const Test&)	// 5
~Test()				// 6
~Test()				// 7
operator=			// 8
~Test()				// 9
~Test()				// 10
~Test()				// 11

来分析一下:

  1. t1构造:Test(int)
  2. t2构造:Test(int)
  3. 实参t1拷贝构造到形参tTest(const Test&)
  4. val构造一个临时的Test对象tmpTest(int)
  5. tmp被拷贝构造到返回值(main函数栈帧上临时的匿名对象):Test(const Test&)
  6. tmp被析构:~Test()
  7. 形参t被析构:~Test()
  8. 返回值(main函数栈帧上临时的匿名对象)赋值给t2operator=
  9. 返回值(main函数栈帧上临时的匿名对象)出了语句t2 = GetObject(t1);就被析构了:~Test()
  10. t2析构:~Test()
  11. t1析构:~Test()

可以看到,这么一段小代码,背后调用了11个函数

但是,我使用的编译器是VS2022,会有返回值优化(RVO)或命名返回值优化(NRVO),把上面分析步骤中的5和6进行了优化,因此实际执行结果如下:

Test(int)			// 1
Test(int)			// 2
Test(const Test&)	// 3
Test(int)			// 4
~Test()				// 7
operator=			// 8
~Test()				// 9
~Test()				// 10
~Test()				// 11

我们分析的时候,还是不要考虑这些编译器的优化,按上面那11步进行分析即可


总结三条对象优化的规则

主要针对上面的代码示例3,总结了三条对象优化的规则

1. 函数参数传递过程中,对象优先按引用传递,这样可以省去一个形参t的拷贝构造调用,形参没有构建新的对象,出作用域也不用析构了,优化了3、7

Test GetObject(Test& t) { ... }

运行结果:

Test(int)			// 1
Test(int)			// 2
Test(int)			// 4
Test(const Test&)	// 5
~Test()				// 6
operator=			// 8
~Test()				// 9
~Test()				// 10
~Test()				// 11

2. 函数返回对象的时候,应该优先返回一个临时对象,而不要返回一个定义过的对象,优化了5、6

Test GetObject(Test& t)
{int val = t.getData();// 直接返回临时对象return Test(val);
}

运行结果:

Test(int)			// 1
Test(int)			// 2
Test(int)			// 4
operator=			// 8
~Test()				// 9
~Test()				// 10
~Test()				// 11

实际上,VS2022编译器的RVO/NRVO已经把这步给优化了,但由于其他编译器不一定有优化,我们还是要写成返回一个临时对象

3. 接收返回值是对象的函数调用的时候,优先按初始化的方式接收,不要按赋值的方式接收,优化了4、8、9

int main()
{Test t1;Test t2 = GetObject(t1);return 0;
}

运行结果:

Test(int)			// 1
Test(int)			// 2
~Test()				// 10
~Test()				// 11

这个看起来优化的很厉害,来分析一下:

在这里插入图片描述
所以,经过一系列优化,现在连这个main函数栈帧的临时对象都不产生了,而是直接构造t2

在这里插入图片描述

至此,从最先开始的11个函数调用优化成了4个函数调用,所以我们还是要牢记这三个原则!!!
(优先引用传递、优先返回临时对象、优先初始化方式接受)

二、CMyString的代码问题

三、

四、添加带右值引用参数的拷贝构造和赋值函数

五、CMyString在vector上的应用

六、move移动语义和forward类型完美转发

七、再聊vector容器使用对象过程中的优化

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

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

相关文章

Python从0到100(三十六):字符和字符集基础知识及其在Python中的应用

1. 字符和字符集概述 字符(Character)是构成书面语言的基本元素&#xff0c;它包括但不限于各国家的文字、标点符号、图形符号和数字。字符集(Character set)则是一个包含多个字符的系统&#xff0c;用于统一管理和编码不同的字符。 常见字符集 ASCII&#xff1a;最早的字符…

SpringBoot 启动流程一

SpringBoot启动流程一 我们首先创建一个新的springboot工程 我们不添加任何依赖 查看一下pom文件 我们创建一个文本文档 记录我们的工作流程 我们需要的是通过打断点实现 我们首先看一下启动响应类 package com.bigdata1421.start_up;import org.springframework.boot.Spr…

音视频流媒体视频平台LntonAIServer视频监控平台工业排污检测算法

在当今社会&#xff0c;环境保护和可持续发展已成为全球关注的焦点。工业生产作为经济发展的重要支柱&#xff0c;其对环境的影响不容忽视。因此&#xff0c;如何有效地监控和管理工业排污&#xff0c;成为了一个亟待解决的问题。LntonAIServer工业排污检测算法应运而生&#x…

开发电商ERP系统需要接入哪些平台API?

跟随全渠道发展趋势&#xff0c;很多实体商家开设电商店铺&#xff0c;为消费者提供便捷的购物体验&#xff0c;增强消费者的满意度&#xff0c;同时也提升了企业自身的市场竞争力。为了满足商家业务拓展需求&#xff0c;很多原本主要服务于实体商贸企业的ERP服务商&#xff0c…

CSS filter(滤镜)属性,并实现页面置灰效果

目录 一、filter&#xff08;滤镜&#xff09;属性 二、准备工作 三、常用的filter属性值 1、blur(px) 2、brightness(%) 3、contrast(%) 4、grayscale(%) 5、opacity(%) 6、saturate(%) 7、sepia(%) 8、invert(%) 9、hue-rotate(deg) 10、drop-shadow(h-shadow v…

编译rust程序,并让它依赖低版本的GLIBC库

在linux环境下编译rust程序,编译好的程序会依赖你当前系统的GLIBC库,也就是说你的程序无法在使用更低版本GLIBC库的linux系统中运行。 查看当前系统的GLIBC版本: strings /lib64/libc.so.6 | grep GLIBC 为了让编译的程序依赖比较低版本的GLIBC库,我们最好在centos7下编译…

JavaScript基础-函数(完整版)

文章目录 函数基本使用函数提升函数参数arguments对象&#xff08;了解&#xff09;剩余参数(重点)展开运算符(...) 逻辑中断函数参数-默认参数函数返回值-return作用域(scope)全局作用域局部作用域变量的访问原则垃圾回收机制闭包 匿名函数函数表达式立即执行函数 箭头函数箭头…

【机器学习】Google开源大模型Gemma2:原理、微调训练及推理部署实战

目录 一、引言 二、模型简介 2.1 Gemma2概述 2.2 Gemma2 模型架构 三、训练与推理 3.1 Gemma2 模型训练 3.1.1 下载基座模型 3.1.2 导入依赖库 3.1.3 量化配置 3.1.4 分词器和模型实例化 3.1.5 引入PEFT进行LORA配置 3.1.6 样本数据清洗与加载 3.1.7 模型训练与保…

SCI一区TOP|徒步优化算法(HOA)原理及实现【免费获取Matlab代码】

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献5.代码获取 1.背景 2024年&#xff0c;SO Oladejo受到徒步旅行启发&#xff0c;提出了徒步优化算法&#xff08;Hiking Optimization Algorithm, HOA&#xff09;。 2.算法原理 2.1算法思想 HOA灵感来自于…

小试牛刀-Solana合约账户详解

目录 一.Solana 三.账户详解 3.1 程序账户 3.2 系统所有账户 3.3 程序派生账户(PDA) 3.4 Token账户 四、相关学习文档 五、在线编辑器 Welcome to Code Blocks blog 本篇文章主要介绍了 [Solana合约账户详解] ❤博主广交技术好友&#xff0c;喜欢文章的可以关注一下❤ …

【人工智能】--生成对抗网络

个人主页&#xff1a;欢迎来到 Papicatch的博客 课设专栏 &#xff1a;学生成绩管理系统 专业知识专栏&#xff1a; 专业知识 文章目录 &#x1f349;引言 &#x1f349;GAN 的基本原理 &#x1f348;生成器&#xff08;Generator&#xff09; &#x1f348;判别器&…

sql语句练习注意点

1、时间可以进行排序&#xff0c;也可以用聚合函数对时间求最大值max&#xff08;时间&#xff09; 例如下面的例子&#xff1a;取最晚入职的人&#xff0c;那就是将入职时间倒序排序&#xff0c;然后limit 1 表&#xff1a; 场景&#xff1a;查找最晚入职员工的所有信息 se…

Pinia:Vue 2 和 Vue 3 中更好用的状态管理框架

前言 还在用Vuex? 在Vue应用程序的开发过程中&#xff0c;高效且易于维护的状态管理一直是开发者关注的核心问题之一。随着Vue 3的发布&#xff0c;状态管理领域迎来了一位新星——Pinia&#xff0c;它不仅为Vue 3量身打造&#xff0c;同时也向下兼容Vue 2&#xff0c;以其简…

PostgreSQL 在Windows下保姆级图文安装教程

&#x1f60e; 作者介绍&#xff1a;我是程序员洲洲&#xff0c;一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主。 &#x1f913; 同时欢迎大家关注其他专栏&#xff0c;我将分享Web前后端开发、人工智能、机器学习、深…

The Plant Cell:DAP-seq技术助力揭示MdWRKY75调控苹果耐热性的分子机制

2024年6月12日&#xff0c;西北农林科技大学作物抗逆与高效生产全国重点实验室/园艺学院苹果抗逆与品质改良创新团队马锋旺教授/李超课题组在植物学知名期刊The Plant Cell&#xff08;影响因子10&#xff09;在线发表了题为“The MdHSC70-MdWRKY75 module mediates basal appl…

微信小程序 调色板

注意&#xff1a;是在uniapp中直接使用的一个color-picker插件&#xff0c;改一下格式即可在微信小程序的原生代码中使用 https://github.com/KirisakiAria/we-color-picker 这是插件的地址&#xff0c;使用的话先把这个插件下载下来&#xff0c;找到src&#xff0c;在项目创…

九、函数的声明和定义

函数声明&#xff1a; 1. 告诉编译器有一个函数叫什么&#xff0c;参数是什么&#xff0c;返回类型是什么。但是具体是不是存在&#xff0c;函数 声明决定不了。 2. 函数的声明一般出现在函数的使用之前。要满足先声明后使用。 3. 函数的声明一般要放在头文件中的。 定义的函…

股价持续低迷,业绩颓势不减,冀光恒难救平安银行?

文&#xff5c;新熔财经 作者&#xff5c;宏一 周一一上班&#xff0c;就听到旁边的同事感慨今年股市行情很不错&#xff0c;尤其是银行股&#xff0c;上半年累计上涨了17.02%&#xff0c;是涨幅最大的板块。 听到这里&#xff0c;我美滋滋地打开自己的账户&#xff0c;结…

spdlog一个非常好用的C++日志库(四): 源码分析之logger类

目录 1.简介 2.类图关系 3.logger数据成员 4.logger函数成员 4.1.构造与析构 4.1.1.构造函数 4.1.2.拷贝构造、移动构造 4.2.交换操作 4.3.log()记录日志消息 4.3.1.格式串 4.3.2.普通字符串 4.3.3.日志级别 4.3.4.宽字符支持 4.4.sink_it_&#xff1a;将log消息…

PLC工作原理

PLC&#xff08;可编程逻辑控制器&#xff09;的工作原理简述为&#xff1a;集中采样、集中输出、周期性循环扫描。 西门子PLC 一、集中采样 顺序读取所有输入端子的通断状态&#xff0c;并将所读取的信息存到输入映像寄存器中&#xff0c;此时输入映像寄存器被刷新&#xff…