关于函数传参的编译器优化问题

1.函数传值

先声明一下函数传值是有两次传递的,先把值转换成需要的类型,再把值传递拷贝给函数内部使用,具体一点比如funcl(3),先把3构造成A对象,在把这个对象拷贝到函数里面

#include <iostream>
using namespace std;
class A
{
	public:
		//构造函数
		A(int a = 0):_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)" << '\n';
			if (this != &aa)//判断是不是自赋值,虽然这里判断看起来没有什么用,但是养成习惯还是有必要的
			{
				_a = aa._a;
			}
			return *this;
		}
		~A(){}
	private:
		int _a;
};
void funcl( A aa)
{
}
int main()
{
	A aa1 = 1;//这里注意一点,没有初始化的=和已经实例化的=调用的构造函数不同
	funcl(aa1);//调用拷贝构造函数,要在一个表达式中才可以优化
	funcl(3);//优化成调用一次构造函数,正常流程是先把3构造成A类,再调用拷贝构造传入实参
	funcl(A(3));//优化成调用一次构造函数,正常流程也是先把3构造成A类,再调用拷贝构造传入实参
    return 0;
}
2.传引用传参

为什么在传参的时候最好使用【传引用传参】,原因就是在于可以减少拷贝,提高程序运行效率

通过调式可以发现,funcl_1(aa2)无论是【构造】还是【拷贝构造】,都不会去调用,这是为什么呢?

原因就在于这里使用的是引用接收,那么形参部分的aa就是aa1的别名,无需构造产生一个新的对象,也不用去拷贝产生一个,直接用形参部分这个就可以了,现在知道引用传参的好处了吧

void funcl_1(const A& aa)
{
	;
}
    A aa2 = 1;
	funcl_1(aa2);//直接引用形参,没有额外构造花费
	funcl_1(3);//调用构造函数,临时对象要使用const,否则就会权限放大
	funcl_1(A(3));//调用构造函数
3.传值返回

如果没有接收返回值那么就只有一次拷贝,如果返回值有接收(指的是没有实例的初始化,已经实例化的会调用赋值重载)还是调用一次拷贝构造,这是因为拷贝+拷贝,编译器会优化成一次拷贝

如果已经被实例化,例如下面的d,会调用赋值重载,拷贝和赋值重载并不能被优化成一次调用,所以尽量还是用初始化的方式接受返回值,以便编译器可以自动优化

A funcl_2()
{
	A aa;
	return aa;
}
    funcl_2();//return返回时调用拷贝构造函数,析构分别在函数内一次,在函数外一次,如果没有接受返回值则立即析构
	A c = funcl_2();//接受返回值,按理来说会产生第二次拷贝,但是编译器会优化成一次拷贝构造,
	A d;
	d = funcl_2();//d已经被实例化,所以会调用赋值重载,且不会和前面的拷贝构造一起优化
4.传引用返回

返回引用跟传入引用相似,因为是返回的一个对象的引用,所以不需要调用拷贝构造,直接返回临时对象,但是这个临时对象在返回引用的时候会自动销毁,以至于传引用其实相当于什么都没有传递,所以要判断是否是临时对象的引用返回

A& funcl_3()
{
	A aa(0);
	return aa;
}
    funcl_3();//没有调用拷贝构造
	A f = funcl_3();//这里虽然调用了一次拷贝构造,但是是对临时对象的拷贝,没有用,所以内部数据还是随机值,这里用值返回可以解决
5.匿名对象返回

匿名对象那肯定是一个临时对象了,所以这里用的是返回值,而不是引用,

A funcl_4()
{
	return A();
}
funcl_4();//构造+拷贝构造优化成了直接构造
	A aa4 = funcl_4();//构造+拷贝构造+拷贝构造优化成了直接构造,这里析构的顺序是按照栈的顺序来析构的
6.完整代码
#include <iostream>
using namespace std;
class A
{
	public:
		//构造函数
		A(int a = 0):_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)" << '\n';
			if (this != &aa)//判断是不是自赋值,虽然这里判断看起来没有什么用,但是养成习惯还是有必要的
			{
				_a = aa._a;
			}
			return *this;
		}
		~A(){}
	private:
		int _a;
};
void funcl( A aa)
{
	;
}
void funcl_1(const A& aa)
{
	;
}
A funcl_2()
{
	A aa;
	return aa;
}
A& funcl_3()
{
	A aa(0);
	return aa;
}
//返回匿名对象
A funcl_4()
{
	return A();
}
int main()
{
	//先声明函数传值是有两次传递的,先把值转换成需要的类型,再把值传递拷贝给函数内部
	//传值
	A aa1 = 1;//这里注意一点,没有初始化的=和已经实例化的=调用的构造函数不同
	funcl(aa1);//调用拷贝构造函数,要在一个表达式中才可以优化
	funcl(3);//优化成调用一次构造函数,正常流程是先把3构造成A类,再调用拷贝构造传入实参
	funcl(A(3));//优化成调用一次构造函数,正常流程也是先把3构造成A类,再调用拷贝构造传入实参
	//传引用
	A aa2 = 1;
	funcl_1(aa2);//直接引用形参,没有额外构造花费
	funcl_1(3);//调用构造函数,临时对象要使用const,否则就会权限放大
	funcl_1(A(3));//调用构造函数
	//在为函数传递参数的时候,尽量使用引用传参,可以减少拷贝的工作,而且有的编译器还不自带优化
	//传值返回
	funcl_2();//return返回时调用拷贝构造函数,析构分别在函数内一次,在函数外一次,如果没有接受返回值则立即析构
	A c = funcl_2();//接受返回值,按理来说会产生第二次拷贝,但是编译器会优化成一次拷贝构造,
	A d;
	d = funcl_2();//d已经被实例化,所以会调用赋值重载,且不会和前面的拷贝构造一起优化
	//传引用返回
	funcl_3();//没有调用拷贝构造
	A f = funcl_3();//这里虽然调用了一次拷贝构造,但是是对临时对象的拷贝,没有用,所以内部数据还是随机值,这里用值返回可以解决
	//匿名对象返回
	funcl_4();//构造+拷贝构造优化成了直接构造
	A aa4 = funcl_4();//构造+拷贝构造+拷贝构造优化成了直接构造,这里析构的顺序是按照栈的顺序来析构的
	return 0;
}
7.总结
  • 拷贝+拷贝=拷贝
  • 尽量使用const + &传参,减少拷贝的同时防止权限放大
  • 接收返回值对象,尽量拷贝构造方式接收,不要赋值接收【会干扰编译器优化】
  • 函数中返回对象时,尽量返回匿名对象【可以增加编译器优化】
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

里科生

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

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

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

打赏作者

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

抵扣说明:

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

余额充值