对象应用优化
我们都知道,C语言和C++在程序执行中,都是通过调用一系列的函数来实现的。并且,很多时候,编译器会帮助我们做一系列的事情,比如(在编译类的成员方法的时候,编译器默认添加 this 指针,以此来确定是哪一个对象调用了该成员方法)。得益于编译器或者说系统帮助我们做的一系列事情,我们可以更加方便地使用C++。但是凡事有利必有弊,因为系统有时候会自己调用一系列的函数,从另一个角度来说,也一定程度上降低了效率。
而我们想要提高C++的执行效率,就需要了解程序(主要是对象)使用过程中都调用了哪些方法。
先来看下面的代码示例:
#include <iostream>
using namespace std;
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;
}
int getdata()const { return ma; }
private:
int ma;
};
int main()
{
Test t1;
Test t2(t1);
Test t3 = t1;
Test t4 = Test(20); //显式生成临时对象,但是与Test t1(20)没有区别
t4 = t1;
return 0;
}
调用过程如下:
上述对象t4
,编译器做了如下的优化:
c++编译器对于对象的构造的优化:用临时对象生成新对象的时候,临时对象就不产生了,直接构造新对象就可以了
Test t1 = Test(20);
t1 = Test(30);//显式生成临时对象:该语句会调用t1.operator=(const Test& t),因为t1已经存在,要完成赋值操作,还必须构建一个临时对象
再看下面的语句:注意注释的解释
t1 = (Test)30;
//隐式转换,编译器会寻找会找构造函数中是否有带整型的构造函数int->Test(int)
t1 = 30;
继续:
Test *p = &Test(20);//p指向的是一个已经析构的临时对象了,所以不安全
const Test &ref = Test(20);//引用相当于给产生的临时对象赋予了别名,ok
上述过程中产生的临时对象,在指针指向的临时对象语句结束后立刻被析构,而引用的却不会被析构,因为相当于给临时对象赋予了别名。
结论:用指针指向临时对象是不安全的,用引用指向临时对象是安全的。
再来看下面的一个示例:注意Test类添加了一个成员变量mb,复习一下之前的几个知识点:
继续来看:以最初的Test类为例:
#include <iostream>
using namespace std;
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;
}
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;
Test t2;
t2 = GetObject(t1);
return 0;
}
短短的几行代码,却调用了这么多次函数:
其实这也就是问题所在了:其具体逻辑如下:
原来的:Test GetObject(Test t)
优化后:Test GetObject(Test& t)
做这一步的目的是,减少参数传递过程中的拷贝构造,并且,也少了形参对象的析构,简而言之,少了两条调用语句:
继续优化:
原来的:Test tmp(val);return tmp;
优化后:return Test(val);
这一步的目的是,减少局部变量tmp的构造,以及tmp的析构。
于是,直接用临时对象取代原来定义好的局部对象,之后用临时对象来构造一个主函数上的新对象;这个时候编译器就会优化,不生成临时对象;直接构造新对象。
所以,在return的地方,会直接构造主函数栈帧上的对象:
其实还可以优化:
原来的:Test t2;t2 = GetObject(t1);
优化后:Test t2 = GetObject(t1);
用主函数栈帧上的临时对象拷贝新对象t2
,于是连这个临时对象都不产生了,直接构造t2
这一步的目的是,省去主函数栈帧上的临时对象的构造和析构,并且省去了赋值函数。
也就是说,这一步优化,省去了3步!
于是我们总结出对象优化的原则:
- 不能返回局部的或者临时对象的指针或引用
- 函数参数传递过程中,对象优先按引用传递,不要按值传递
- 函数返回对象的时候,应该优先返回一个临时对象,而不要返回一个定义过的对象
- 接收返回值是对象的函数调用的时候,优先按初始化的方式接收,不要按赋值的方式接收