一文搞懂C++临时对象优化 附详细样例

引入

C语言和C++在程序执行中,都是通过调用一系列的函数来实现的。并且,很多时候,编译器会帮助我们做一系列的事情,比如(在编译类的成员方法的时候,编译器默认添加 this 指针,以此来确定是哪一个对象调用了该成员方法)。得益于编译器或者说系统帮助我们做的一系列事情,我们可以更加方便地使用C++。但是凡事有利必有弊,因为系统有时候会自己调用一系列的函数,从另一个角度来说,也一定程度上降低了效率。
而我们想要提高C++的执行效率,就需要了解程序(主要是对象)使用过程中都调用了哪些方法。

测试环境

VS2019 x86环境下,编译器会影响最终结果的,如下面那个例子,VS2019 x86环境下是11个函数,而g++编译器则是9个函数,多余的优化下例有讲。

例子

#include <iostream>
using namespace std;

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;
	}
	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)
Test(int)
Test(const Test&)
Test(int)
Test(const Test&)
~Test()
~Test()
operator=
~Test()
~Test()
~Test()

共调用了11个函数
我们可以来追踪一下

Test GetObject(Test t) //3.Test(const Test& t)
{
	int val = t.getdata();
	Test tmp(val);		//4.Test(int)
	return tmp;			//5.Test(const Test&) 因为tmp是局部对象,是出不了该函数这个花括
                        //号的,故需要临时传给外面main函数中的一个临时对象才能传出。(最
                        //起码vs2019编译器是这样做的)经测试g++编译器会对此进行优化,少了
                        //main函数的那个临时对象,因此总共就9个函数
}						//6.tmp析构 7.t的析构

int main()
{
	Test t1;	//1.Test(int)
	Test t2;	//2.Test(int)
	t2 = GetObject(t1);//8.Test& operator=(const Test& t) 9.用来传递的那个临时对象的析构

	return 0;
}					//10和11分别为 t1 t2 的析构

image.png
接下来可以进行讲解:

int main()
{
	Test t1;			//调用的是正常的构造函数
	Test t2(t1);		//Test(const Test&)
	Test t3 = t1;		//Test(const Test&)
	Test t4 = Test(20); //显式生成临时对象,但是与Test t1(20)没有区别
	t4 = t1;			//operator=
    t4=Test(30);		//Test(int)  operator=
	return 0;
}
Test(int)
Test(const Test&)
Test(const Test&)
Test(int)
operator=
Test(int)
operator=
~Test()
~Test()
~Test()
~Test()
~Test()

故对此可以总结:
c++编译器对于对象的构造的优化:用临时对象生成新对象的时候,临时对象就不产生了,直接用临时对象的值去构造新对象。
记住:只要是用临时对象去生成新对象的时候,临时对象都不会产生,不管是什么形式的临时对象。然而用临时对象去赋值已有对象时,临时对象便会生成。

故基于如上讲解我们可以对刚开始的例子进行优化(注释的地方即为改动)

Test GetObject(Test& t)
{
	int val = t.getdata();
	//Test tmp(val);
	return Test(val);
}

int main()
{
	Test t1;
	//Test t2;
	//t2 = GetObject(t1);
	Test t2 = GetObject(t1);

	return 0;
}
  1. 我们首先将函数的参数改为引用,对此节约了一个临时变量,函数-2
  2. 再取消tmp变量的构建,直接返回临时变量,同时把主函数中的t2的赋值操作改为初始化操作,触发优化条件:当用临时变量生成新变量的时候会进行优化,故省略掉了 tem变量 和 主函数中用来传递返回值的临时变量,函数-5(tmp 和 临时变量的构造和析构 t2的赋值)

故得到如下结果:

Test(int)
Test(int)
~Test()
~Test()

拓展

Test *p = &Test(20);		//p指向的是临时对象了,到下一行时临时对象会析构,造成悬垂指针
const Test &ref = Test(20);	//引用相当于给产生的临时对象赋予了别名,故临时对象不会销毁

结论:用指针指向临时对象是不安全的,用引用指向临时对象是安全的。

总结

于是我们总结出对象优化的原则:

  1. 不能返回局部的或者临时对象的指针或引用
  2. 函数参数传递过程中,对象优先按引用传递,不要按值传递
  3. 函数返回对象的时候,应该优先返回一个临时对象,而不要返回一个定义过的对象
  4. 接收返回值是对象的函数调用的时候,优先按初始化的方式接收,不要按赋值的方式接收
Python是一门支持面向对象编程的语言,它通过(Class)和对象(Object)的概念实现了这一特性。以下是关于Python面向对象编程的一般理解: 1. **(Class)**:是一种模板或蓝图,用于创建具有相似属性和行为的对象定义了数据成员(如变量)和方法(函数),它们描述了对象的状态和行为。 2. **对象(Object)**:对象的实例,它是现实世界的一个实体。每个对象都具有特定的属性值,并能够执行其中定义的方法。 3. **属性(Attribute)**:中的变量就是对象的属性,它们可以存储数据。比如,一个人对象可能有姓名、年龄这样的属性。 4. **方法(Method)**:中的函数是对象的行为,例如获取信息(getter)、设置信息(setter)、执行动作等。比如,人的可能会有一个方法“说话”(speak)。 5. **封装(Encapsulation)**:将数据和操作数据的代码打包成,隐藏实现细节,仅对外提供接口访问,保护数据的安全性。 6. **继承(Inheritance)**:子可以继承父的属性和方法,通过"IS-A"关系实现代码复用。子可以添加新的属性和方法,也可以覆盖或扩展父的方法。 7. **多态(Polymorphism)**:同一种行为可以在不同的对象上表现出不同的形式,包括静态多态(方法重载)和动态多态(方法重写或虚函数)。 8. **构造函数(Constructor)**:特殊的方法,当创建新对象时自动执行,通常用于初始化对象的属性。 9. **析构函数(Destructor)**:特殊的方法,在对象生命周期结束时自动执行,用于清理资源。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值