542-C++对象的优化

对象使用过程中背后调用了哪些方法

代码示例1

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 t4=Test(20);是临时对象先构造,然后拿临时对象拷贝构造t4,然后语句结束,临时对象析构。
到底是不是这样?
在这里插入图片描述
我们来运行看看
在这里插入图片描述
t1是调用普通的构造函数。t2和t3都是调用拷贝构造函数。
t4是调用普通的构造函数。并没有向我们刚才说的那样。

int main()
{
	Test t1;//调用构造函数 
	Test t2(t1);//调用拷贝构造函数 
	Test t3 = t1;//调用拷贝构造函数,因为t3还没有生成 
	
	//Test(20) 显示生成临时对象,是没有名字的,所以其生存周期:所在的语句
	//语句结束,临时对象就析构了 
	/*
	C++编译器对于对象构造的优化:用临时对象生成新对象的时候,临时对象
	就不产生了,直接构造新对象就可以了
	*/
	Test t4 = Test(20);//和Test t4(20);没有区别的!
	cout << "--------------" << endl;
	return 0;
}

我们接下去再看

int main()
{
	Test t1;//调用构造函数 
	Test t2(t1);//调用拷贝构造函数 
	Test t3 = t1;//调用拷贝构造函数,因为t3还没有生成 

	Test t4 = Test(20);//和Test t4(20);没有区别的!
	cout << "--------------" << endl;

	//t4.operator=(t2)
	t4 = t2;//调用赋值函数,因为t4原本已存在 


	//Test(30)显式生成临时对象
	//t4原本已存在,所以不是构造,这个临时对象肯定要构造生成的 
	//临时对象生成后,给t4赋值 
	//出语句后,临时对象析构 
	//t4.operator=(const Test &t)
	t4 = Test(30);
	cout << "--------------" << endl;
	return 0;
}

在这里插入图片描述
我们接下去再看

int main()
{
	Test t1;
	Test t2(t1);
	Test t3 = t1;
	Test t4 = Test(20);
	cout << "--------------" << endl;
	t4 = t2;//t4调用赋值重载函数
	
	t4 = Test(30);
	//显式生成临时对象,赋值给t4,出语句后,临时对象析构 
	
	t4 = (Test)30;//把30强转成Test类型int->Test(int)
	//把其他类型转成类类型的时候,编译器就看这个类类型
	//有没有合适的构造函数 把整型转成Test,就看这个类类型有没有
	//带int类型参数的构造函数 ,有,就可以显式生成临时对象,然后
	//赋值给t4 出语句后,临时对象析构 

	//隐式生成临时对象,然后赋值给t4,出语句后,临时对象析构 
	t4 = 30;
	//把整型转成Test,Test(30) int->Test(int)  char*->Test(char*)

	cout << "--------------" << endl;
	return 0;
}

在这里插入图片描述
我们接下去再看

int main()
{
	Test t1;
	Test t2(t1);
	Test t3 = t1;
	Test t4 = Test(20);
	cout << "--------------" << endl;
	t4 = t2;	
	t4 = Test(30);
	t4 = (Test)30;
	t4 = 30
	cout << "--------------" << endl;

	Test *p = &Test(40);//指针指向临时对象,这个临时对象肯定是要生成的
	//然后p指向这个临时对象的地址
	//出语句后,临时对象析构 
	//此时p指向的是一个已经析构的临时对象,p相当于野指针了 
	
	const Test &ref = Test(50);//引用一个临时对象,这个临时对象也是要生成的
    //出语句后,临时对象不析构,因为引用相当于是别名,临时对象出语句析构是因为没有名字 
	//用引用变量引用临时对象是安全的,临时对象就是有名字了,临时对象的生存周期就变成引用变量的
	//生存周期了。引用变量是这个函数的局部变量,return完,这个临时对象才析构 
	cout << "--------------" << endl;
	return 0;
}

在这里插入图片描述

代码示例2

class Test
{
public:
	//因为a,b有默认值所以构造有3种方式:
	//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;
};

在这里插入图片描述

//对象的构造顺序标识:1,2,3...14
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()

	//(50,50)是逗号表达式,(表达式1,表达式2,表达式n)
	//(50,50)的最后的结果是最后一个表达式n的结果 50
	//(50, 50) =  (Test)50; Test(int)
	t2 = (Test)(50, 50);//7.Test(int,int) operator= 出语句调用~Test()

	t2 = 60;//Test(int) 8.Test(int,int) operator=出语句调用~Test()
	Test* p1 = new Test(70, 70);//9. Test(int,int) 要调用delete才析构对象
	Test* p2 = new Test[2];//10. Test(int,int) Test(int,int) 要调用delete才析构对象
	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:
	//有默认值,可以有2种构造方式:Test()  Test(20)
	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;
};

在这里插入图片描述
这个方法接收一个Tesl类型的对象,然后把Test对象的值ma取出来赋给val,然后拿val值来构造函数里的局部对象,然后把这个局部对象返回回去。
在这里不能返回指针或者引用,因为返回指针或者引用,要保证函数结束,这个对象还存在,如果对象不存在了,指针间接地访问这个对象的内存就非法访问了。所以不能返回局部对象或者临时对象的地址或者引用。
不能返回局部的或者临时对象的指针或引用

下面这个对象的地址就可以在局部函数中返回了:
因为这个对象是在数据段,程序运行开始,内存就有了,第一次运行到它的时候,构造这个对象,整个程序运行结束,它才进行对象的析构。

在这里插入图片描述
我们继续分析下面这个代码:
在这里插入图片描述
当我们运行时,打印的结果是什么样的?
在这里插入图片描述

#include <iostream>
using namespace std;
class Test
{
public:
	//有默认值,可以有2种构造方式:Test()  Test(20)
	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;//1、调用带整型参数的构造函数 
	Test t2;//2、调用带整型参数的构造函数
	t2 = GetObject(t1);//函数调用,实参传递给形参,是初始化还是赋值?
	//当然是初始化,对象初始化是调用构造函数,赋值是两个对象都存在 调用左边对象的=重载
	//t1是已经构造好的Test对象,而形参是t是正在定义的Test对象 
	//3、调用Test(const Test&) 拿t1拷贝构造形参t
	//4、调用Test(int)的构造,构造tmp对象 然后return tmp;tmp是不能直接给t2赋值的
	//因为tmp和t2是两个不同函数栈帧上的对象,是不能直接进行赋值的 GetObject函数完成调用时
	//tmp对象作为局部对象就析构了 ,为了把返回值带出来, 在return tmp;这里,首先要在main函数栈帧
	//上构建一个临时对象,目的就是把tmp对象带出来, 
	//5、调用 Test(const Test&),tmp拷贝构造main函数栈帧上的临时对象
	//6、出 GetObject作用域,tmp析构
	//7、形参t对象析构
	//8、operator =,把main函数刚才构建的临时对象赋值给t2,临时对象没名字,出了语句就要析构 
	//9、把main函数刚才构建的临时对象析构 
	//10、main函数结束,t2析构
	//11、t1析构 

	return 0;
}

在这里插入图片描述

总结三条对象优化的规则

  1. 函数参数传递过程中,对象优先按引用传递,这样可以省去一个形参t的拷贝构造调用,形参没有构建新的对象,出作用域也不用析构了,所以不要按值传!
  2. 函数返回对象的时候,应该优先返回一个临时对象,而不要返回一个定义过的对象
  3. 接收返回值是对象的函数调用的时候,优先按初始化的方式接收,不要按赋值的方式接收

我们看下面代码示例
在这里插入图片描述
我们该如何对上面代码优化呢?

1、函数参数传递过程中,对象优先按引用传递,这样可以省去一个形参t的拷贝构造调用,形参没有构建新的对象,出作用域也就不用析构了,所以不要按值传哦!
在这里插入图片描述
没有t1的拷贝构造,形参t没有新的对象,出作用域也不用析构。
省去了形参t的拷贝构造和形参t的析构
在这里插入图片描述

2、函数返回对象的时候,应该优先返回一个临时对象,而不要返回一个定义过的对象
在这里插入图片描述
t1和t2都是调用带整型参数的构造函数。
然后实参t1到形参t是传引用,没有对象产生。
return Test(val); 返回的是一个临时对象,我们想的是调用一个带整型参数的构造函数生成临时对象,语句中的临时对象出不了GetObject这个函数的,GetObject函数一结束,栈帧回退,这个临时对象就析构了。所以,为了把这个临时对象带出去,只能在main函数的栈帧上用这个临时对象拷贝构造一个新的临时对象。
但是用一个临时对象拷贝构造一个新对象,C++编译器都会去优化,临时对象就不产生了,而是用产生临时对象的方式直接构造新对象。

所以,return Test(val); 时这个临时对象是不产生的。
是直接在main函数的栈帧上构造这个临时对象就可以了。

在这里插入图片描述
所以出GetObject函数作用域,不用进行局部对象的析构了。因为这个临时对象没有产生。
然后就是拿main函数栈帧的这个临时对象对t2进行赋值,出语句后,main函数栈帧上的临时对象析构。
然后就是t2析构,t1析构。
这次优化,少了tmp的构造和析构。
在这里插入图片描述

3、接收返回值是对象的函数调用的时候,优先按初始化的方式接收,不要按赋值的方式接收
在这里插入图片描述
1、调用普通构造函数构造t1
2、是给t2初始化的过程。先处理GetObject函数调用。实参到形参没有产生对象。
3、return Test(val);只产生main函数栈帧上的临时对象
4、然后用这个临时对象给t2初始化!用这个临时对象拷贝构造同类型的新对象t2。C++编译器会进行优化,这个main函数栈帧上的临时对象都不产生了,直接构造t2对象。
在这里插入图片描述
也就是return Test(val);直接构造t2对象了
Test t2= GetObject(t1);在汇编上,除了把t1的地址传进去,还把t2的地址也传进去了,也压到函数栈帧上,所以return Test(val);就可以取到t2的地址,就知道在哪块内存上构造一个名为t2的对象。

然后析构t2
然后析构t1

Test GetObject(Test &t) 
{
	int val = t.getData();
	return Test(val); 
}
int main()
{
	Test t1;
	Test t2= GetObject(t1);
	
	return 0;
}

在这里插入图片描述

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林林林ZEYU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值