当前位置: 首页 > news >正文

对象被优化以后才是高效的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容器使用对象过程中的优化

相关文章:

  • 达梦数据库 页大小与数据库字段长度的关系
  • LeetCode题解:205. 同构字符串,哈希表,JavaScript,详细注释
  • 第一周java。2
  • 桥接模式与适配器模式
  • QT截屏,截取控件为图片,指定范围截屏三种截屏方式
  • 【HTML入门】第一课 - 网页标签框架
  • stm32中IIC通讯协议
  • Xilinx FPGA:vivado关于真双端口的串口传输数据的实验
  • Spring Boot与Spring Batch的深度集成
  • iOS开发中用到的自定义UI库
  • 【Kubernetes学习】
  • Python容器 之 字典--字典的遍历
  • 性能测试、负载测试、压力测试、稳定性测试简单区分【超详细】
  • Solr安装IK中文分词器
  • 【Linux开发实战指南】基于TCP、进程数据结构与SQL数据库:构建在线云词典系统(含注册、登录、查询、历史记录管理功能及源码分享)
  • 【每日笔记】【Go学习笔记】2019-01-10 codis proxy处理流程
  • ABAP的include关键字,Java的import, C的include和C4C ABSL 的import比较
  • Codepen 每日精选(2018-3-25)
  • Flannel解读
  • JavaScript对象详解
  • MySQL的数据类型
  • Objective-C 中关联引用的概念
  • PAT A1120
  • tab.js分享及浏览器兼容性问题汇总
  • uni-app项目数字滚动
  • ViewService——一种保证客户端与服务端同步的方法
  • 订阅Forge Viewer所有的事件
  • 如何利用MongoDB打造TOP榜小程序
  • 由插件封装引出的一丢丢思考
  • 怎样选择前端框架
  • TPG领衔财团投资轻奢珠宝品牌APM Monaco
  • 测评:对于写作的人来说,Markdown是你最好的朋友 ...
  • 我们雇佣了一只大猴子...
  • ​LeetCode解法汇总307. 区域和检索 - 数组可修改
  • #APPINVENTOR学习记录
  • ${factoryList }后面有空格不影响
  • ()、[]、{}、(())、[[]]命令替换
  • (2)关于RabbitMq 的 Topic Exchange 主题交换机
  • (pojstep1.1.2)2654(直叙式模拟)
  • (第61天)多租户架构(CDB/PDB)
  • (附源码)计算机毕业设计SSM智能化管理的仓库管理
  • (转)创业家杂志:UCWEB天使第一步
  • (转贴)用VML开发工作流设计器 UCML.NET工作流管理系统
  • *_zh_CN.properties 国际化资源文件 struts 防乱码等
  • .Net 6.0 处理跨域的方式
  • .net core 6 使用注解自动注入实例,无需构造注入 autowrite4net
  • .NET Core 通过 Ef Core 操作 Mysql
  • .NET Framework 4.6.2改进了WPF和安全性
  • .NET 简介:跨平台、开源、高性能的开发平台
  • ??myeclipse+tomcat
  • @AutoConfigurationPackage的使用
  • [12] 使用 CUDA 进行图像处理
  • [2010-8-30]
  • [20180129]bash显示path环境变量.txt
  • [52PJ] Java面向对象笔记(转自52 1510988116)