引入
C语言和C++在程序执行中,都是通过调用一系列的函数来实现的。并且,很多时候,编译器会帮助我们做一系列的事情,比如(在编译类的成员方法的时候,编译器默认添加 this 指针,以此来确定是哪一个对象调用了该成员方法)。得益于编译器或者说系统帮助我们做的一系列事情,我们可以更加方便地使用C++。但是凡事有利必有弊,因为系统有时候会自己调用一系列的函数,从另一个角度来说,也一定程度上降低了效率。 而我们想要提高C++的执行效率,就需要了解程序(主要是对象)使用过程中都调用了哪些方法。
测试环境
VS2019 x86环境下,编译器会影响最终结果的,如下面那个例子,VS2019 x86环境下是11个函数,而g++编译器则是9个函数,多余的优化下例有讲。
例子
#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(int)
Test(int)
Test(const Test&)
Test(int)
Test(const Test&)
~Test()
~Test()
operator=
~Test()
~Test()
~Test()
共调用了11个函数 我们可以来追踪一下
Test GetObject(Test t)
{
int val = t.getdata();
Test tmp(val);
return tmp;
}
int main()
{
Test t1;
Test t2;
t2 = GetObject(t1);
return 0;
}
接下来可以进行讲解:
int main()
{
Test t1;
Test t2(t1);
Test t3 = t1;
Test t4 = Test(20);
t4 = t1;
t4=Test(30);
return 0;
}
Test(int)
Test(const Test&)
Test(const Test&)
Test(int)
operator=
Test(int)
operator=
~Test()
~Test()
~Test()
~Test()
~Test()
故对此可以总结: c++编译器对于对象的构造的优化:用临时对象生成新对象的时候,临时对象就不产生了,直接用临时对象的值去构造新对象。 记住:只要是用临时对象去生成新对象的时候,临时对象都不会产生,不管是什么形式的临时对象。然而用临时对象去赋值已有对象时,临时对象便会生成。
故基于如上讲解我们可以对刚开始的例子进行优化(注释的地方即为改动)
Test GetObject(Test& t)
{
int val = t.getdata();
return Test(val);
}
int main()
{
Test t1;
Test t2 = GetObject(t1);
return 0;
}
- 我们首先将函数的参数改为引用,对此节约了一个临时变量,函数-2
- 再取消tmp变量的构建,直接返回临时变量,同时把主函数中的t2的赋值操作改为初始化操作,触发优化条件:当用临时变量生成新变量的时候会进行优化,故省略掉了 tem变量 和 主函数中用来传递返回值的临时变量,函数-5(tmp 和 临时变量的构造和析构 t2的赋值)
故得到如下结果:
Test(int)
Test(int)
~Test()
~Test()
拓展
Test *p = &Test(20);
const Test &ref = Test(20);
结论:用指针指向临时对象是不安全的,用引用指向临时对象是安全的。
总结
于是我们总结出对象优化的原则:
- 不能返回局部的或者临时对象的指针或引用
- 函数参数传递过程中,对象优先按引用传递,不要按值传递
- 函数返回对象的时候,应该优先返回一个临时对象,而不要返回一个定义过的对象
- 接收返回值是对象的函数调用的时候,优先按初始化的方式接收,不要按赋值的方式接收
|