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

一.传值和传引用区别

传值和传引用

传值 : 在调用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
在这里插入图片描述

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

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

おすすめ

転載: blog.csdn.net/DR5200/article/details/118945798