读者如果觉得我文章还不错的,希望可以多多支持下我,文章可以转发,但是必须保留原出处和原作者署名。更多内容请关注我的微信公众号:cpp手艺人
先来看两段代码执行效率是一样?
//oa的一系列操作...
OptimizationA GetOpt()
{
OptimizationA oa;
//oa的一系列操作...
return oa;
}
void GetOpt(OptimizationA &_result)
{
// result的一系列操作...
return;
}
思考:效率是一样的?如果是不一样的,那么又是如何不一样的?那我们如何做效率更好呢?
程序语义的转化
我们自己写的代码,自己看一回事,但是在编译器的角度来看又是一番风景。所以这次我们换个角度来看待问题,分别从初始化操作、优化、成员列表初始化三个方面探究下编译器会怎么翻译我们的代码。
1.初始化操作
A.显式初始化操作
OptimizationA oe;
OptimizationA of(oe);
OptimizationA og = oe;
OptimizationA oh = OptimizationA(oe);
// 编译器的角度看,分成两步走,
// 第一步:定义变量(不会调用初始化操作),第二步:调用拷贝构造
// 1.OptimizationA of (注意此时不会调用OptimizationA的默认构造函数)
// 2.of.OptimizationA::OptimizationA(oe) (调用拷贝构造函数)
// 3.og.OptimizationA::OptimizationA(oe) (调用拷贝构造函数)
// 4.oh.OptimizationA::OptimizationA(oe) (调用拷贝构造函数)
B.参数初始化
void Parameter(OptimizationA oa)
{
}
{
OptimizationA tempoa;
Parameter(tempoa);
}
// 编译器生成的代码
OptimizationA _tempObj<font>;
// tempObj调用copy构造
tempObj.OptimizationA::OptimizationA(tempoa);
Parameter(tempObj);
// tempObj调用析构函数,销毁对象
tempObj.OptimizationA::~OptimizationA();
C.返回值初始化
OptimizationA GetOpt()
{
OptimizationA oa;
return oa;
}
// 此为编译器的生成的函数,分为两步操作
// 第一步:将上面的函数重写为下面的带引用参数的形式
void GetOpt(OptimizationA &_result)
{
OptimizationA oa;
//oa的一系列操作。。。。。。
// 第二步:在return返回之前,调用result的copy 构造函数
result::OptimizationA::OptimizationA(oa);
return;
}
// 下面是编译器生成的调用代码
// 1.形式转换成这样
OptimizationA result;
GetOpt(result);
// 2.如果用户调用了类成员函数
GetOpt().GetHello();
// 编译器则转换成这样
(GetOpt(result), result).GetHello();
// 3.如果是用户定义了函数指针
OptimizationA (*pf)();
pf = GetOpt; // 没有参数
// 编译器则转换成这样
void (*pf)(OptimizationA &);
(pf(result), result).GetHello();
2.优化
A.用户层面优化
// 程序员的未优化
OptimizationA GetOpt(const T &y, const T &x)
{
OptimizationA oa(x, y);
// oa其他操作
return oa;
}
// 在linux上测试需要关闭优化选项
// 先是生成了一个临时对象tempobj,然后调用tempobj的拷贝构造函数,将oa的数据拷贝到
// tempobj中,然后在调用oa的析构函数。
// 这个过程中消耗了一个tempobj的拷贝构造和析构函数
// 程序员优化,这样做就少了一个临时对象的生成和销毁
OptimizationA GetOpt(const T &x, const T &y)
{
return OptimizationA(x, y);
}
未优化代码 | 优化代码 |
---|---|
Linux上关闭优化选项结果: compiler:1 level:2 call ctor compiler:2 level:3 call copy ctor compiler:1 level:2 call dtor compiler:3 level:4 call copy ctor compiler:2 level:3 call dtor compiler:3 level:4 call dtor Linux不关闭优化选项: compiler:1 level:2 call ctor compiler:1 level:2 call dtor windows上: compiler:1 level:2 call ctor compiler:2 level:3 call copy ctor compiler:1 level:2 call dtor compiler:2 level:3 call dtor |
Linux: compiler:1 level:2 call ctor compiler:1 level:2 call dtor 在windows上: compiler:1 level:2 call ctor compiler:1 level:2 call dtor |
B.编译器优化
// 程序员写的代码
OptimizationA GetOpt()
{
OptimizationA oa;
return oa;
}
// 编译器生成的代码:(named return value (NRV))
// 分为两步操作
// 第一步:将上面的函数重写为下面的带引用参数的形式
void GetOpt(OptimizationA &_result)
{
OptimizationA oa;
//oa的一系列操作...
// 第二步:在return返回之前,调用__result的copy 构造函数
__result::OptimizationA::OptimizationA(oa);
return;
}
3.成员列表初始化
先来看段代码:
class InitialzationB
{
public:
// InitialzationB()
// {}
InitialzationB(int value): m_IA(value), m_a(value), m_b(value)
/*
放在初始化列中……
1.如果是在成员列表初始化,站在编译器的角度看
m_IA.InitialzationA::InitialzationA(value)
*/
{
/*
放在构造函数中…..
m_IA = value;
2.如果是在函数内部初始化,站在编译器的角度看
A.先是生成一个临时对象
InitialzationA oc;
oc.InitialzationA::InitialzationA(value);
B.在m_IA的copy ctor
m_IA.InitialzationA::InitialzationA(oc);
C.临时对象再去销毁
oc.InitialzationA::~InitialzationA();
所以成员变量初始化会提高效率,但只针对类类型变量,对基本类型无影响。
在初始化列表中,不要用类成员变量去初始化另外一个成员变量
*/
}
private:
InitialzationA m_IA; // 自定义class
int m_a;
int m_b;
};
A.成员列表初始化含义
InitialzationB(int value): m_IA(value), m_a(value), m_b(value) 这就是初始化列表的调用方法
B.为什么需要初始化列表,以及初始化列表调用时机
简单来说为了初始化对象时的效率。看上面的代码第7行放在初始化列中,从编译器的角度看就是直接调用了InitialzationA的构造函数。但是你如果放在16行,那么在编译器的角度看就是先生成了一个InitialzationA临时对象,在调用m_IA的copy构造函数,然后临时对象的消亡调用析构函数。所以大费周章的构造对象造成效率的下降。 调用时机:编译器会在构造函数之前会插入一段额外的代码,这就是initialization list。然后在执行用户写的代码。
C.注意事项
A.有四种情况必须放到初始化列表中
1. 成员变量是个引用 |
2. 成员变量是const类型 |
3. 成员变量是带参数的构造函数类类型 |
4. 基类有带参数的构造函数 |
B.初始化列表的初始化顺序
初始化顺序是按照在类中的声明顺序的来决定。所以在类的初始化列表中还是严格按照类中声明的顺序来复制。
比如:
class InitialzationB
{
public:
// InitialzationB()
// {}
// InitialzationB(int value): m_IA(value) , m_b(value), m_a(m_b)
// 正宗做法
InitialzationB(int value): m_IA(value), m_a(value), m_b(value)
{
}
private:
InitialzationA m_IA;
int m_b;
int m_a;
};
C.在初始化列表中调用成员函数
不要在初始化列表中调用成员函数,因为你不知道这个函数以后会多么的依赖当前的对象。
总结:
现在我们开始回答上面提出的问题,第一个方法至少消耗了一个ctor,copy ctor, dtor,同时还要考虑编译器的实现,中间可能还会temp object的生成,又会增加一个copy ctor,dtor。反过来再看方法二只消耗了ctor,dtor。效率肯定比方法一高。 知道了编译器做了什么,和怎么做的。这将有助于对C++语言背后的实现细节更了若指掌,才能写出高效的程序。同时也看出来c++为了追求效率,背后做了很多我们不知道的事情。最后假如我们是编译器,我们会如何生成代码的?这是值得我们思考的地方。