跳至主要內容

C++对象的优化

张威大约 8 分钟c/c++对象优化

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;
};

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;
}

C++编译器对于对象构造的优化

用临时对象给已存在对象赋值

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

,所以不是构造,,给t4赋值出语句后,临时对象析构

显示生成临时对象和隐式生成临时对象

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;
}

示例二

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)
  • t2 = (Test)(50, 50);由于将(50,50)等价于50,所以最后调用Test(50)

  • 注意Test t5(100, 100);//2.Test(int, int)mian()前,因为****

  • 析构顺序: p1->p2(析构2次)->p4->t3->t2->t4->t5->t1

2、函数调用过程中对象被后续调用的方法太多

#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;
}

3、总结三条对象优化原则

  1. 函数参数传递过程中,对象优先按,这样可以

  2. 函数返回对象的时候,应该优先返回一个,而

  3. 收返回值是对象的函数调用的时候,优先按的方式接收

Test GetObject(Test& t)	//引用传递
{
	int val = t.getData();
	//Test tmp(val);
	//return tmp;
	//返回临时对象
	return Test(val);
}

int main()
{
	Test t1;

	Test t2 = GetObject(t1);
	//t2 = GetObject(t1);

	return 0;
}