C++幕后故事(三)--程序语义转化

读者若是以为我文章还不错的,但愿能够多多支持下我,文章能够转发,可是必须保留原出处和原做者署名。更多内容请关注个人微信公众号:cpp手艺人linux

先来看两段代码执行效率是同样?

//oa的一系列操做...
OptimizationA GetOpt() {
    OptimizationA oa;
    //oa的一系列操做...
    return oa;
}

void GetOpt(OptimizationA &_result) {
    // result的一系列操做...
    return;
}
复制代码

思考:效率是同样的?若是是不同的,那么又是如何不同的?那咱们如何作效率更好呢?c++

程序语义的转化

咱们本身写的代码,本身看一回事,可是在编译器的角度来看又是一番风景。因此此次咱们换个角度来看待问题,分别从初始化操做、优化、成员列表初始化三个方面探究下编译器会怎么翻译咱们的代码。程序员

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.成员列表初始化

先来看段代码:windows

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.在初始化列表中调用成员函数

不要在初始化列表中调用成员函数,由于你不知道这个函数之后会多么的依赖当前的对象。spa

总结:

如今咱们开始回答上面提出的问题,第一个方法至少消耗了一个ctor,copy ctor, dtor,同时还要考虑编译器的实现,中间可能还会temp object的生成,又会增长一个copy ctor,dtor。反过来再看方法二只消耗了ctor,dtor。效率确定比方法一高。 知道了编译器作了什么,和怎么作的。这将有助于对C++语言背后的实现细节更了若指掌,才能写出高效的程序。同时也看出来c++为了追求效率,背后作了不少咱们不知道的事情。最后假如咱们是编译器,咱们会如何生成代码的?这是值得咱们思考的地方。翻译

相关文章
相关标签/搜索