类和对象——拷贝对象时的一些编译器优化

server/2025/3/5 5:00:07/

拷贝对象时的一些编译器优化

  • 拷贝对象时的一些编译器优化
    • 案例1:仅使用类中的成员函数
    • 案例2:案例1减少一次拷贝构造
    • 案例3:临时对象也具有常属性
    • 案例4:const引用延长生命周期
    • 案例5:传匿名对象传参
    • 案例6:函数传值返回时的优化
    • 案例7:优化的条件
    • 案例8:隐式类型转换的优化
  • 再次理解封装

拷贝对象时的一些编译器优化

在传参和传返回值的过程中,一般编译器会做一些优化(也就是说有的不做优化),减少对象的拷贝,这个在一些场景下还是非常有用的。

这里只举几个案例,详细见书籍《深度探索c++对象模型》。

在20世纪末流行的编译器(例如,vc++6.0)不会对这种情况进行优化。

案例1:仅使用类中的成员函数

很多时候,生成这个对象的目的仅仅是为了调用类中的某个函数。此时没必要生成一个对象,特别是生成一个对象作为实参上传给普通函数。

#include<iostream>
#include<cstdlib>
using namespace std;class A {
public:A(int a = 6):a(a) {cout << "A(int a)" << endl;}A(const A& aa):a(aa.a) {cout << "A(const A& aa)" << endl;}A& operator=(const A& aa) {cout << "A& operator=(const A& aa)" << endl;if (this != &aa) {a = aa.a;}return *this;}~A() {cout << "~A()" << endl;}void print() {using std::cout;cout << a << "\n";}
private:int a;
};//若调用拷贝构造仅仅是为了调用这个函数,完全没必要传值传参
void f1_1(A a) {a.print();
}//所以直接加引用
void f1_2(A& a) {a.print();
}void f1() {A a;f1_1(a);cout << endl;f1_2(a);cout << endl;
}int main() {f1();return 0;
}

案例2:案例1减少一次拷贝构造

首先,const对象不能调用非const成员函数。所以const对象也要准备对应的const函数重载。

其次,引用和const一般在一起,为了避免别名修改原来的对象(变量)。

最后,形参使用引用可以减少一次拷贝构造。

#include<iostream>
#include<cstdlib>
using namespace std;class A {
public:A(int a = 6):a(a) {cout << "A(int a)" << endl;}A(const A& aa):a(aa.a) {cout << "A(const A& aa)" << endl;}A& operator=(const A& aa) {cout << "A& operator=(const A& aa)" << endl;if (this != &aa) {a = aa.a;}return *this;}~A() {cout << "~A()" << endl;}//相应函数也要对这个类的成员函数进行限制防止权限放大void print() const {using std::cout;cout << a << "\n";}
private:int a;
};//为了支持生成临时对象,使用const引用
void f2_1(const A& a) {a.print();
}void f2_2(A& a) {a.print();
}void f2_3(A& a) {//非const形参,不具有常属性a.print();
}void f2() {A a;f2_1(a);//权限缩小cout << endl;f2_2(a);//权限平移cout << endl;//f2_3(A());//权限放大f2_1(A());//形参也具有常属性时权限平移,可以调用cout << endl;
}int main() {f2();return 0;
}

输出:

A(int a)
66A(int a)
6
~A()~A()

f2_3(A());无法编译通过,因为临时对象、匿名对象都有常属性,上传无常属性形参的函数,权限放大。

案例3:临时对象也具有常属性

在案例2已经证明匿名对象具有常属性。隐式类型转换的临时对象也具有常属性。

#include<iostream>
#include<cstdlib>
using namespace std;class A {
public:A(int a = 6):a(a) {cout << "A(int a)" << endl;}A(const A& aa):a(aa.a) {cout << "A(const A& aa)" << endl;}A& operator=(const A& aa) {cout << "A& operator=(const A& aa)" << endl;if (this != &aa) {a = aa.a;}return *this;}~A() {cout << "~A()" << endl;}//相应函数也要对这个类的成员函数进行限制防止权限放大void print() const {using std::cout;cout << a << "\n";}
private:int a;
};//const引用能很好的支持生成临时对象
void f3_1(const A& a) {//这个地方引用和const一般同时出现防止不小心修改a.print();
}void f3() {//少调用一次拷贝构造f3_1(A());//匿名对象有常属性cout << endl;f3_1(A(4));cout << endl;f3_1(3);//临时对象也具有常属性cout << endl;
}int main() {f3();return 0;
}

输出:

A(int a)
6
~A()A(int a)
4
~A()A(int a)
3
~A()

它们都被优化成了只调用一次构造函数。

案例4:const引用延长生命周期

const引用可以延长临时对象的生命周期,本质是将临时对象变成有名对象,这样临时对象就可以像有名对象一样生命周期在局部。

#include<iostream>
#include<cstdlib>
using namespace std;class A {
public:A(int a = 6):a(a) {cout << "A(int a)" << endl;}A(const A& aa):a(aa.a) {cout << "A(const A& aa)" << endl;}A& operator=(const A& aa) {cout << "A& operator=(const A& aa)" << endl;if (this != &aa) {a = aa.a;}return *this;}~A() {cout << "~A()" << endl;}//相应函数也要对这个类的成员函数进行限制防止权限放大void print() const {using std::cout;cout << a << "\n";}
private:int a;
};//缺省值为匿名对象
//const延长生命周期使得匿名对象存在于局部
void f4_1(const A& a = A()) {a.print();
}void f4() {f4_1();cout << endl;//这里只有ref出了作用域,//临时对象的生命周期才终止const A& ref = A();cout << endl;ref.print();//还在{}也就是作用域内,可以使用cout << endl;
}int main() {f4();return 0;
}

案例5:传匿名对象传参

编译器优化情况1:隐式类型转换作为实参,此时会调用两次构造。编译器将连续的两次构造(构造+拷贝构造)优化为直接构造。

c++标准并没有对这种情况进行优化说明,这个其实还是编译器本身的行为。在一些年代比较久远的编译器(比如20世纪末)就不会。

#include<iostream>
#include<cstdlib>
using namespace std;class A {
public:A(int a = 6):a(a) {cout << "A(int a)" << endl;}A(const A& aa):a(aa.a) {cout << "A(const A& aa)" << endl;}A& operator=(const A& aa) {cout << "A& operator=(const A& aa)" << endl;if (this != &aa) {a = aa.a;}return *this;}~A() {cout << "~A()" << endl;}//相应函数也要对这个类的成员函数进行限制防止权限放大void print() const {using std::cout;cout << a << "\n";}
private:int a;
};void f5_1(A a) {a.print();
}//析构void f5_2(const A a) {a.print();
}A f5_3() {A a;return a;
}//隐式类型,连续构造(两次及以上)->优化为直接构造
void f5() {//传值传参//正常情况A a;//构造f5_1(a);//拷贝构造cout << endl;// 一个表达式中,构造+拷贝构造->优化为一个构造f5_1(A());//匿名对象构造+拷贝构造被优化cout << endl;f5_1(A(3));cout << endl;f5_1(4);//隐式类型转换cout << endl;//这个也是构造+拷贝构造A b = A(3);cout << endl;
}int main() {f5();return 0;
}

输出:

A(int a)
A(const A& aa)
6
~A()A(int a)
6
~A()A(int a)
3
~A()A(int a)
4
~A()A(int a)~A()
~A()

分析:

f5_1(A());f5_1(A(3));:匿名对象调用构造函数,加拷贝构造生成形参。

f5_1(4);:隐式转换,一次构造加拷贝构造。

A b = A(3);:一次构造加拷贝构造。

这三种情况,都被优化为一次构造。

案例6:函数传值返回时的优化

#include<iostream>
#include<cstdlib>
using namespace std;class A {
public:A(int a = 6):a(a) {cout << "A(int a)" << endl;}A(const A& aa):a(aa.a) {cout << "A(const A& aa)" << endl;}A& operator=(const A& aa) {cout << "A& operator=(const A& aa)" << endl;if (this != &aa) {a = aa.a;}return *this;}~A() {cout << "~A()" << endl;}//相应函数也要对这个类的成员函数进行限制防止权限放大void print() const {using std::cout;cout << a << "\n";}
private:int a;
};A f6_1() {A a;//构造return a;//拷贝构造生成临时对象
}A& f6_2() {A a;return a;
}void f6() {A a;cout << endl;f6_1();cout << endl;a = f6_1();cout << endl;A ret = f6_1();cout << endl;A ret2 = f6_2();cout << endl;
}int main() {f6();return 0;
}

输出:

A(int a)A(int a)
A(const A& aa)
~A()
~A()A(int a)
A(const A& aa)
~A()
A& operator=(const A& aa)
~A()A(int a)
A(const A& aa)
~A()A(int a)
~A()
A(const A& aa)~A()
~A()
~A()

单独看A ret = f6_1();这种情况:
请添加图片描述

A f6_1()return语句会生成临时对象,但编译器进行了优化,直接将这个a在生命周期结束前拷贝给ret

所以在一个表达式的连续两个步骤里,局部对象构造 + 传值返回生成临时对象调用拷贝构造,两次调用构造被优化为一次。

A ret2 = f6_2();因为f6_2是传引用返回,所以直接省去了return语句的一次拷贝构造,在析构前生成临时对象,之后通过拷贝构造将对象拷贝给ret2

案例7:优化的条件

#include<iostream>
#include<cstdlib>
using namespace std;class A {
public:A(int a = 6):a(a) {cout << "A(int a)" << endl;}A(const A& aa):a(aa.a) {cout << "A(const A& aa)" << endl;}A& operator=(const A& aa) {cout << "A& operator=(const A& aa)" << endl;if (this != &aa) {a = aa.a;}return *this;}~A() {cout << "~A()" << endl;}//相应函数也要对这个类的成员函数进行限制防止权限放大void print() const {using std::cout;cout << a << "\n";}
private:int a;
};A f7_1() {A a;return a;
}void f7() {//这种情况编译器不会再优化A ret2;ret2 = f7_1();
}int main() {f7();return 0;
}

f7()这种情况不能优化,两个原因:

  • 同类型才能优化(都是构造或都是拷贝构造才能优化,这里是构造和赋值)。
  • 不在同一步骤(声明对象和赋值重载是两个语句或者说步骤)。

案例8:隐式类型转换的优化

和案例6的情况相似,都是构造临时对象并返回,只是存在隐式类型转换。所以被优化为一次构造。

#include<iostream>
#include<cstdlib>
using namespace std;class A {
public:A(int a = 6):a(a) {cout << "A(int a)" << endl;}A(const A& aa):a(aa.a) {cout << "A(const A& aa)" << endl;}A& operator=(const A& aa) {cout << "A& operator=(const A& aa)" << endl;if (this != &aa) {a = aa.a;}return *this;}~A() {cout << "~A()" << endl;}//相应函数也要对这个类的成员函数进行限制防止权限放大void print() const {using std::cout;cout << a << "\n";}
private:int a;
};//被优化为直接构造
//构造匿名对象加临时对象,两次构造被优化为1次
A f8_1() {return A();
}A f8_2() {return 8;
}A f8_3() {return A(1);
}void f8() {A a1 = f8_1();cout << endl;A a2 = f8_2();//隐式类型转换cout << endl;A a3 = f8_3();cout << endl;
}int main() {f8();return 0;
}

所以就有了这样一个特性:局部对象都只能传值返回,因此可以的话尽可能使用临时对象返回或隐式类型转换,可以减少拷贝调用次数。

再次理解封装

现实生活中的实体计算机并不认识,计算机只认识二进制格式的数据。如果想要让计算机认识现实生活中的实体,用户必须通过某种面向对象的语言,对实体进行描述,然后通过编写程序,创建对象后计算机才可以认识。

比如想要让计算机认识洗衣机,就需要:

  1. 用户先要对现实中洗衣机实体进行抽象——即在人为思想层面对洗衣机进行认识,洗衣机有什么属性,有那些功能,即对洗衣机进行抽象认知的一个过程。

  2. 经过1之后,在人的头脑中已经对洗衣机有了一个清醒的认识,只不过此时计算机还不清楚,想要让计算机识别人想象中的洗衣机,就需要人通过某种面向对象的语言(比如:c++、java、python等)将洗衣机用类来进行描述,并输入到计算机中。

  3. 经过2之后,在计算机中就有了一个洗衣机类,但是洗衣机类只是站在计算机的角度对洗衣机对象进行描述的,通过洗衣机类,可以实例化出一个个具体的洗衣机对象,此时计算机才能洗衣机是什么东西。

  4. 用户就可以借助计算机中洗衣机对象,来模拟现实中的洗衣机实体了。

所以类是对某一类实体对象来进行描述的,描述该对象具有那些属性,那些方法,描述完成后就形成了一种新的自定义类型,才用该自定义类型就可以实例化具体的对象。


http://www.ppmy.cn/server/172515.html

相关文章

Pany-v2:LFI漏洞探测与敏感文件(私钥窃取/其他)自动探测工具

地址:https://github.com/MartinxMax/pany 关于Pany-v2 Pany-v2 是一款 LFI&#xff08;本地文件包含&#xff09;漏洞探测工具&#xff0c;具备自动识别敏感文件的能力。它能够利用 LFI 漏洞检测并提取 id_rsa 私钥、系统密码文件以及其他可能导致安全风险的敏感信息。该工具…

磁盘阵列新秀GSx并行文件存储是HPC高性能计算/AI 大模型-1替3好省预算

Infortrend 普安存储GSx 并行文件存储系统凭一体化设计&#xff0c;颠覆了传统存储系统的复杂配置模式。内置并行文件系统&#xff0c;支持私有协议或 CIFS 协议&#xff0c;实现客户端/服务器与存储设备的直接连接,无需额外配置I/O节点、元数据服务器及并行系统软件&#xff0…

Xsens动作捕捉+AI训练家用机器人:迈向智能生活的新篇章

人形机器人技术的进步正在推进家用机器人走进千家万户&#xff0c;未来家用机器人将成为家庭中的重要成员。Xsens动作捕捉技术与AI训练的深度融合&#xff0c;为家用机器人的智能化发展注入了新的活力&#xff0c;在本文中我们将为大家介绍动捕AI训练的全新方式在机器人开发中能…

Virtual Box虚拟机安装Mac苹果Monterey和big sur版本实践

虚拟机安装苹果实践&#xff0c;在Windows10系统&#xff0c;安装Virtual Box7.1.6&#xff0c;安装虚拟苹果Monterey版本Monterey (macOS 12) 。碰到的主要问题是安装光盘不像Windows那么容易拿到&#xff0c;而且根据网上很多文章制作的光盘&#xff0c;在viritualBox里都无法…

c++头文件和命名空间

在 C 中&#xff0c;头文件和命名空间是两个重要的概念&#xff0c;它们分别用于代码组织和作用域管理。 一、头文件&#xff08;Header Files&#xff09; 1. 作用 声明接口&#xff1a;存放函数、类、变量的声明&#xff08;而非定义&#xff09;。代码复用&#xff1a;通…

Sharp4CWH.exe:分享一个通过 Console Window Host 白名单文件绕过安全防护监控的工具

Sharp4CWH.exe 是一款通过调用 Windows 系统的白名单文件 Console Window Host 启动指定二进制文件进程的工具。该工具利用 Windows 系统的白名单机制&#xff0c;能够通过启动 Console Window Host&#xff0c;该进程通常被认为是系统信任的进程&#xff0c;来运行指定的二进制…

AI数据分析:deepseek生成SQL

在当今数据驱动的时代&#xff0c;数据分析已成为企业和个人决策的重要工具。随着人工智能技术的快速发展&#xff0c;AI 驱动的数据分析工具正在改变我们处理和分析数据的方式。本文将着重介绍如何使用 DeepSeek 进行自动补全SQL 查询语句。 我们都知道&#xff0c;SQL 查询语…

《从0到1:用Python在鸿蒙系统开发安防图像分类AI功能》

在人工智能与移动应用深度融合的当下&#xff0c;类目标签AI功能成为众多行业提升效率和用户体验的关键技术。本文聚焦于HarmonyOS NEXT API 12及以上版本&#xff0c;以图像分类在智能家居安防领域的应用为例&#xff0c;为开发者详细阐述如何利用Python开发类目标签AI功能&am…