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

C++对象拷贝时的优化编译

在现代编译器中,当我们在 C++中进行对象的拷贝操作时,编译器并非只是机械地执行逐字节的复制。相反,它会进行优化,避免不必要的拷贝构造等等,这种优化包括“返回值优化”(RVO),“拷贝省略” 等等。以下代码运行结果是在vs2022下实现的。

#include<iostream>
using namespace std;class A
{
public:A(int a = 0):_a1(a){cout << "A(int a)" << endl;}//拷贝构造A(const A& aa):_a1(aa._a1){cout << "A(const A& aa)" << endl;}A& operator=(const A& aa){cout << "A& operator=(const A& aa)" << endl;if (this != &aa){_a1 = aa._a1;}return *this;}//析构~A(){cout << "~A()" << endl;}
private:int _a1 = 1;
};
void f1(A aa)
{}
A f2()
{A aa;return aa;
}

1.内置类型转换为自定义类型的优化

int main()
{A aa = 1;return 0;
}

    在语法上来看,也就是没有优化的情况下,(1)编译器先调用默认构造函数,在栈上创建临时对象,传入参数值1初始化临时对象的成员变量,(2)再将这个临时对象去拷贝构造给aa,(3)aa被初始化成功后析构这个临时对象,(4)等到程序结束时再析构aa

    从下面的结果来看,也就是优化之后,并没有调用拷贝构造,(1)编译器可能直接调用默认构造函数初始化aa,而不创建临时对象和调用拷贝参数,(2)然后在程序结束时析构aa。

2.传值传参时的优化

void f1(A aa)
{}
int main()
{A aa1;f1(aa1);retrun 0;
}

从语法上看,(1)当执行A aa1时,编译器会调用A类的默认构造函数。由于没有提供参数,默认构造函数A(int n = 0)会被调用,_a1被初始化为0。(2)随后执行f1(aa1),编译器会调用A类拷贝构造函数将aa1的值拷贝到临时对象aa中,(3)然后进入函数f1, 该函数结束时调用析构函数析构aa,(4)最后main函数结束时析构aa1。从结果上来看,此处没有优化。

3.隐式类型

void f1(A aa)
{}
int main()
{	//隐式类型,连续构造+拷⻉构造->优化为直接构造f1(1);return 0;
}

从语法上来看,(1)当调用f1(1)时,编译器调用A的默认构造函数将整数1隐式转换为A类对象(2)进入函数f1,由于f1的参数是按值传递的,A类的临时对象aa被创建,并且刚刚创建的对象会被拷贝构造到临时对象aa中,(3)当函数f1结束时,析构aa。当main函数结束时,此处由于没有其它对象,所以没有再调用析构函数。

优化过之后,(1)编辑器可能直接会直接在f1的参数位置调用默认构造函数,使用参数1进行构造,(2)而不是先创建一个临时对象再拷贝,因此没有拷贝构造。(3)在f1内部,aa直接引用构造函数,到函数f1结束时,析构aa。得到了下面的结果                                                                                                                                                   

4.传值返回

A f2()
{A aa;return aa;
}
int main()
{// 传值返回// 返回时⼀个表达式中,连续拷⻉构造+拷⻉构造->优化⼀个拷⻉构造 (vs2019 debug)// ⼀些编译器会优化得更厉害,进⾏跨⾏合并优化,直接变为构造。(vs2022 debug)//构造+拷贝构造-》直接构造A aa2 = f2();
}

从语法上来看,(1)当调用A aa2 = f2();时,程序进入f2函数,首先创建局部对象aa,调用默认构造函数初始化aa,(2)返回时会触发拷贝构造函数,创建一个临时对象用于返回值传递,(3)在main函数中临时对象初始化aa2再次触发拷贝构造,(4)当f2函数执行完毕,编译器会调用aa的析构函数,(5)当main函数结束时调用aa2的析构函数

优化后,(1)在函数f2中调用了默认构造函数创建一个A类对象aa2,(2)从结果上来看,编译器可能是只创建了aa2,将aa类似一个指针的效果指向aa2,因此只在在main函数结束时调用了析构函数析构aa2,而没有析构aa

A f2()
{A aa;cout << &aa2 << endl;return aa;
}
int main()
{cout << &aa2 << endl;A aa2 = f2();
}	

从下面的图中我们可以看到,aa和aa2的地址是相同的。 

这篇文章主要目的是为了介绍编译器的对代码的优化,以及学习程序运行的语法,可能有表述不严谨的地方,欢迎大家探讨纠正。


http://www.mrgr.cn/news/28903.html

相关文章:

  • LLamaindex基本使用
  • 06-1_间隙锁和临键锁的锁定范围
  • JVM 调优篇7 调优案例4- 线程溢出
  • COMDEL电源CPS500AS RF13.56MHZ RF Generator手侧
  • 好代码网同款wordpress主题,完全开源无加密可二开
  • 优惠充值话费api对接如何选择对接平台?
  • 最短路径算法
  • JavaScript考核详解
  • 淘宝npm镜像源更新后,如何正常使用npm命令
  • 使用C++程序编写5 个浮点数,求平均值后输出
  • STL相关简介
  • 削峰+限流:秒杀场景下的高并发写请求解决方案
  • Vscode运行Python无法导入自己编写的包的解决方法
  • 【数据结构初阶】顺序结构二叉树(堆)接口实现超详解
  • Flet全平台开发:软件开发界勇士为Python语言补短板的一次极具挑战性的尝试、冲刺和华丽亮相
  • AWS认证SAA-C03每日一题
  • python中Web开发框架的使用
  • Java4----String
  • 绿咖啡豆缺陷检测系统源码分享
  • (十六)、把镜像推送到私有化 Docker 仓库