目录
1.接收函数返回值对象:直接通过拷贝构造接收函数返回值对象。
2.接收函数返回值对象:先定义一个对象,然后再赋值接受函数返回值对象。
3.对象接收函数返回值对象时,函数中返回对象时尽量返回匿名对象。
前言
在传参和传返回值的过程中,一般编译器会做一些优化,以此来减少对象的拷贝,这个在一些场景下还是比较有用的,这种优化可以在一定程度上提高程序运行的效率。接下来我将用若干个程序运行样例来了解对象拷贝时的编译器的一些优化。
下面简单明实现一个类:
#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& a)" << endl;
}
A& operator=(const A& aa)
{
cout << "operator=()" << endl;
if(this!=&aa)
_a = aa._a;
return *this;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
一.
A func1()
{
A aa;//构造
return aa;//拷贝构造
} //构造+拷贝构造 不可以优化为 一次构造函数 也可以理解为 aa的构造 和 拷贝临时对象
//(即func1的返回值)不在一行 因此不可以优化
1.接收函数返回值对象:直接通过拷贝构造接收函数返回值对象。
int main()
{
A a = func1();//函数func1()
return 0;
}
运行结果如下:
我们可以看到,对象a通过func1()的返回值来拷贝构造它本身。
A((int a)) : func1()里的对象aa调用的构造函数
A(const A& a) : fun1()的返回值是A而不是A&所以fun1()中的aa进行拷贝构造一个临时对象来作为func1()的返回值,但是对象main函数中的对象a要以fun1()的返回值来拷贝构造对象a,因此应该调用两次拷贝构造,那为什么结果是一次呢,这是因为编译器将这两次拷贝构造优化为了一次。
~A() : fun1()调用结束后fun1()创建的栈帧销毁,栈帧里的对象aa调用的析构函数
~A() : main函数中的对象a调用析构函数
2.接收函数返回值对象:先定义一个对象,然后再赋值接受函数返回值对象。
int main()
{
A a;
a = func1();
return 0;
}
通过上面两种案例可以看到,当对象需要接收函数返回值对象时,尽量使用拷贝构造方式接收,
赋值接收效率能低一点。那可以再提升一下对象接收返回值对象的效率吗?其实可以 下面我们来看一下怎样提高。
3.对象接收函数返回值对象时,函数中返回对象时尽量返回匿名对象。
A func2()
{
return A();//构造+拷贝构造->构造函数 可以理解为 A()的构造 和 拷贝临时对象(即func2的返回值)
//在一行进行
}
int main()
{
A a = func1();
cout<<"-------------------"<<endl;
A a1 = func2();//构造+(拷贝构造+拷贝构造->拷贝构造)->构造函数
cout<<"--------------------"<<endl;
return 0;
}
根据编译结果可以看出对象a1通过func2()的返回值对象来拷贝构造a1,仅仅只调用了一次构造函数就完成了,这就是编译器暗中进行了优化。在func2()以匿名对象A()作为其返回值,本来A()要先调用构造函数,再进行1.(拷贝构造)构造出一个临时对象作为func2()的返回值,fun2()调用结束后,fun2()的返回值来2.(拷贝构造),编译器将1.(拷贝构造)和2.(拷贝构造)合并为一个拷贝构造,再将这一个拷贝构造和构造优化为了一个构造,所以最后只调用了一次构造函数。
二.
1.函数传参尽量使用const&传参
void func3(A aa)
{
}
int main()
{
A a = 1;//构造+拷贝构造->构造
cout<<"------------------"<<endl;
func3(a);
cout<<"------------------"<<endl;
func3(1);
cout<<"------------------"<<endl;
func3(A(1));
cout<<"------------------"<<endl;
return 0;
}
运行结果如下:
接下来我解释一下各个执行结果:
A ((int a)) :首先整型1隐式类型转换为类型为A的临时对象,这一过程调用一次构造函数,临时对象再对对象a进行拷贝构造,这里编译器将构造+拷贝构造优化为了一次直接构造。
-----------------------------
A(const A& a) : 向func3()传参时对象a拷贝构造fun3()的函数参数aa。
~A :func3()调用结束后其栈帧会销毁,参数aa(存在 fun3()的栈帧中即栈区)会随着栈帧销毁而析构。
------------------------------
A((int a)) :整型1先隐式类型转换为类型为A的临时对象,这一过程调用了一次构造函数,临时对象再对对象a进行拷贝构造,这里编译器将构造+拷贝构造优化为了一次直接构造。
~A :函数func3()中的参数对象aa析构。
------------------------------
A((int a)) :构造匿名对象A(1)调用了一次构造函数,匿名对象再拷贝构造func3()的函数参数对象aa,这里编译器将构造+拷贝构造优化为了一次直接构造。
~A :函数func3()中的参数对象aa析构。
------------------------------
~A :程序结束a调用析构函数。
void func4(const A& aa)
{
}
int main()
{
func4(A());
cout<<"-------------------"<<endl;
func4(1);
return 0;
}
根据上面程序运行结果我们可以得到函数传参时,使用引用传参编译器是没有进行优化的。
三.建议
1.接收返回值对象,尽量以拷贝构造的方式接收,避免赋值接收。
2.函数返回对象时尽量返回匿名对象。
3.函数传参尽量使用const&传参。