zoukankan      html  css  js  c++  java
  • [Effective C++ --030]透彻了解inlining的里里外外

    引言  inline函数

    在函数声明或定义中函数返回类型前加上关键字inline即把min()指定为内联。 

    inline函数对编译器而言必须是可见的,以便它能够在调用点内展开该函数。与非inline函数不同的是,inline函数必须在调用该函数的每个文本文件中定义。当然,对于同一程序的不同文件,如果inline函数出现的话,其定义必须相同。

    为保证不会发生这样的事情,建议把inline函数的定义放到头文件中。在每个调用该inline函数的文件中包含该头文件。这种方法保证对每个inline函数只有一个定义,且程序员无需复制代码,并且不可能在程序的生命期中引起无意的不匹配的事情。

    第一节 C++ inline函数的使用

    inline函数,可以调用它们而又不需蒙受函数调用所招致的额外开销。

    当你inline某个函数,或许编译器就因此又能力对它(函数本体)执行语境相关最优化。
    然而,inline函数背后的整体观念是,将“对此函数的每一个调用”都已函数本体替换之,这样做可能增加你的目标码(object code)大小。在内存有限的机器上,过度inline会造成程序体积太大,导致换页行为,降低缓存的命中率等一些带来效率损失的行为。
    如果inline函数的本体很小,编译器针对“函数本体”所产生的码可能比针对“函数调用”所产出的码更小。将函数inline可以导致更小的目标码,从而提高效率。

    1.inline函数的声明:

    inline只是对编译器的一个申请,不是强制命令。这种申请可以隐喻提出也可以明确提出。

    隐喻方式:

    1 class Person{ 
    2 public: 
    3     ... 
    4     int age() const {return theAge;}               //一个隐喻的inline申请 
    5     ... 
    6 private: 
    7     int theAge; 
    8 };

    明确声明:

    1 template<typename T> 
    2 inline const T& std::max(const T& a, const T& b)   // 明确申请inline
    3 { 
    4     return a < b? b: a; 
    5 }

    inline函数通常放置在头文件内,因为大多数建置环境(build environments)在编译过程中进行inlining,为了将“函数调用”替换为“被调用函数的本体”,编译器必须知道那个函数长什么样子。

    templates 通常也被置于头文件内,因为他一旦被引用,编译器(在编译期)为了将它具现化,需要知道它长什么样子。如果template没有理由要求它所具现的每个函数都是inlined,就应该避免将这个template声明为inline(不论显式还是隐式)。

    2.inlining与virtual

    大部分编译器拒绝将太过复杂(带有循环或递归)的函数inlining,而所有对virtual函数的调用也都会使inlining落空。因为:

    inline:执行前,先将调用动作替换为被调用函数的本体

    virtual:等待,直到运行期才确定调用哪个函数

    有时,虽然编译器有意愿inlining某个函数,还是可能为该函数生成一个函数本体。例如,如果程序要取某个inline函数的地址,编译器通常必须为此函数生成一个outlined函数本体。编译器通常不对“通过函数指针而进行的调用”实施inlining,这意味着对inline函数的调用有可能inlined,也可能不被inlined:

    1 inline void f(){…}      //假设编译器有意愿inline“对f的调用”
    2 void (*pf)() = f;
    3 f();                    //这个调用将被inlined,因为是一个正常调用
    4 pf();                   //这个调用或许不被inlined,因为通过指针达成

    即时你从未使用函数指针,“未被成功inlined”的inline函数还是有可能缠住你,因为程序猿并非唯一要求函数指针的人!有时候编译器会生成构造函数和析构函数的outlined副本,如此一来他们就可以获得指针指向那些函数吗,在array内部元素的构造和析构过程中使用。

    3.inline与构造/析构函数

    实际上构造函数和析构函数往往都是inlining的糟糕候选人。

     1 class base { 
     2 public: 
     3     ... 
     4 private: 
     5     std::string bm1, bm2; 
     6 };
     7 
     8 class Derived : public Base { 
     9 public: 
    10     Derived(){}                     //Derived 构造函数是空的 是吗? 
    11     ... 
    12 private: 
    13     std::string dm1, dm2, dm3; 
    14 };

    这个构造函数看起来是inlining的绝佳候选人,因为他根本不含任何代码,但是你的眼睛会欺骗你!

    c++对于“对象被创建和被销毁时发生什么事”做了各式各样的保证。编译器为稍早说的那个表面上看起来是空的Derived构造函数所产生的代码,相当于以下所列:

     1 Derived::Derived() 
     2 { 
     3    Base::Base(); 
     4     try{dm1.std::string::string();} 
     5     catch(...){ 
     6         Base::~Base(); 
     7         throw; 
     8     } 
     9     try{dm2.std::string::string();} 
    10     catch(...){ 
    11         dm1.std::string::~string(); 
    12         Base::~Base(); 
    13         throw; 
    14     } 
    15     try{dm3.std::string::string();} 
    16     catch(...){ 
    17         dm2.std::string::~string(); 
    18         dm1.std::string::~string(); 
    19         Base::~Base(); 
    20         throw; 
    21     } 
    22 }

    这段代码并不能代表编译器真正制造出来的代码,但是不论编译器在其内所做的异常处理多么精致复杂,Derived构造函数至少一定会陆续调用其成员变量和base class两者的构造函数,而那些调用(它们自身也可能被inlined)会影响编译器是否对此空白函数inlining。

    相同的理由也适用于Base构造函数。

    程序库设计者必须评估“将函数声明为inline”的冲击:inline函数无法随着程序库的升级而升级。f是程序库内的一个inline函数,客户将“f函数本体”编进其程序中,一旦程序库设计者决定改变f,所有用到f的客户端程序都必须重新编译。然而若f是non-inline函数,客户端只要重新连接就好了,如果是程序库采用动态链接,升级后的函数甚至可以不知不觉的被应用程序吸纳。
    从实用观点出发,大部分调试器对inline函数都束手无策。

    ◆总结

    1.将大多数inlining限制在小型及被频繁调用的函数身上。这可使日后的调试过程和二进制升级(binary upgradability)更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。

    2.不要只因为function templates出现在头文件,就将它们声明为inline。

  • 相关阅读:
    DW吃瓜课程——机器学习理论知识笔记(四)
    DW吃瓜课程——机器学习理论知识笔记(三)
    DW吃瓜课程——机器学习理论知识笔记(二)
    DW吃瓜课程——机器学习理论知识笔记(一)
    DataWhale编程实践——区块链学习笔记
    CV入门系列笔记——全球人工智能技术创新大赛【热身赛】CV异常检测赛道
    强化学习入门笔记系列——DDPG算法
    强化学习入门笔记系列——稀疏奖赏和模仿学习
    强化学习入门笔记系列——DQN算法
    Java_SPI思想
  • 原文地址:https://www.cnblogs.com/hustcser/p/4226278.html
Copyright © 2011-2022 走看看