编译器对连续的构造函数和拷贝构造函数的优化

Posted DR5200

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编译器对连续的构造函数和拷贝构造函数的优化相关的知识,希望对你有一定的参考价值。

一.传值和传引用区别

传值和传引用

传值 : 在调用f1(a1)函数时,会首先进行压参数,即用 a1 拷贝构造 aa,会调用拷贝构造函数,f1函数调用结束后,aa对象销毁,执行析构函数,接着main函数调用结束,a1对象销毁,执行析构函数

传引用 : 在调用f1(a1)函数时,aa 是 a1 的引用,不会调用拷贝构造函数,main函数调用结束,a1对象销毁,执行析构函数

可以看到传引用比起传值少调用了一次拷贝构造和一次析构函数,提高了效率

#include<iostream>
using namespace std;
class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
	A(const A& a)
	{
		cout << "A(const A& a)" << endl;
	}
	A& operator=(const A& a)
	{
		cout << "A& operator=(const A& a)" << endl;
		return *this;
	}
};
//void f1(const A& aa)
//{}
//void f1(A aa)
//{}
int main()
{
	A a1;
	f1(a1);
}

传值

传引用

二.编译器所做的优化

再来看下面这个案例,在一个表达式中,我们在函数调用时用临时对象去拷贝构造aa,按照上面所讲,会先调用构造函数构造匿名对象,再调用拷贝构造函数去构造aa,但是编译器可能会对此过程进行优化,直接调用构造函数去构造aa

#include<iostream>
using namespace std;
class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
	A(const A& a)
	{
		cout << "A(const A& a)" << endl;
	}
	A& operator=(const A& a)
	{
		cout << "A& operator=(const A& a)" << endl;
		return *this;
	}
};
void f1(A aa)
{}
int main()
{
	f1(A());
}

三. 例题

(1).

#include<iostream>
using namespace std;
A f2()
{
	static A aa;
	return aa;
}
int main()
{
	f2();
}

先构造aa,由于是传值返回,会拷贝构造一个临时对象在main函数的栈帧里,main函数调用结束后,临时对象销毁,调用析构函数,静态变量销毁,调用析构函数


(2).

#include<iostream>
using namespace std;
A& f2()
{
	static A aa;
	return aa;
}
int main()
{
	f2();
}

先构造aa,由于是传引用返回,不会调用拷贝构造函数,最后aa对象销毁,调用析构函数

(3).

#include<iostream>
using namespace std;
A f2()
{
	static A aa;
	return aa;
}
int main()
{
	A a = f2();
}

先构造aa,由于是传值返回,会拷贝构造一个临时对象在main函数的栈帧里,用临时对象再次拷贝构造a,但编译器有可能会进行一些优化,不产生临时对象,直接拿aa对象拷贝构造a对象

在一个表达式中,连续多个构造函数,可能会被编译器优化,优化成一次

(4).

#include<iostream>
using namespace std;
A f2()
{
	static A aa;
	return aa;
}
int main()
{
	A a;
	a = f2();
}

先构造a对象,再构造aa对象,由于是传值返回,会拷贝构造一个临时对象在main函数的栈帧里,用临时对象赋值给a对象,注意此时不再是拷贝构造了,因此编译器不会再进行优化,析构临时对象,析构a对象,析构aa对象

(5). 下面代码共调用多少次拷贝构造函数?

#include<iostream>
using namespace std;
A f(A aa)
{
	A copy1(aa);
	A copy2 = copy1;
	return copy2;
}
int main()
{
	A a;
	A ret = f(f(a));
}

先构造a对象,第一次调用 f 函数,用a拷贝构造aa对象,用aa拷贝构造copy1对象,用copy1拷贝构造copy2对象,原本应由copy2对象在main函数栈帧里拷贝构造一个临时对象,第二次函数调用时,用临时对象拷贝构造aa对象,经过编译器优化之后,直接用copy2拷贝构造aa对象,析构copy2,copy1,aa

第二次函数调用中,拷贝构造copy1,copy2,用copy2拷贝构造 ret 对象,析构copy2,copy1,aa,ret,a

通过以上例题,总结一下:

如果编译器进行优化,只有构造和拷贝构造才会被优化合并,并且必须是在表达式连续的步骤中,优化掉的都是临时对象或匿名对象

以上是关于编译器对连续的构造函数和拷贝构造函数的优化的主要内容,如果未能解决你的问题,请参考以下文章

不要轻视拷贝构造函数与赋值函数

构造函数的调用规则

c++ 编译器会绕过拷贝构造函数

c++拷贝构造函数

拷贝构造函数

C++类和对象中