条款30:透彻了解inlining的里里外外

来源:互联网 发布:excel编程 编辑:程序博客网 时间:2024/04/30 21:44
inline函数比宏好用得多(TK2),编译器通常被设计用来浓缩那些“不含函数调用”的代码,所以编译器有力能对inline函数执行语境相关最优化
但inline也有另一面,对函数调用都以函数本体替换之,会增加目标代码的大小(object code)。在内存有限的环境中,造成的代码膨胀会导致换页行为,导致效率损失。换言之,如果inline函数本体很小,编译器针对“函数本体”产生比“函数调用“更小代码。inline只是对编译器的一个申请而非强制命令,有明确提出和隐喻方式两种。隐喻方式是将函数定义于class中。如下代码:
class  person         {                   public:                            ...                            int age() const {return  theAge;} //隐喻的inline申请,定义于类中                   ...                            private:                            inttheAge;         };


明确声明方式是在之前加inline关键字:
template<typename T>int line const T& std::max(const T& a,const T&b)  //关键字inline 明确申请{return a<b?b:a;}

inline函数通常一定置于头文件中,因为大多数内建环境(build enviroment)在编译过程中进行inlining,少数例外如.NET CLI在运行时内联。大多数C++是编译期行为。
template虽然通常也置于头文件内,但具体化与inline无关,如果所写的template没要求它所实现的每一个函数都是inline,就应该避免将该template申明为Inline(无论显隐);大多数编译器拒绝太过复杂(如循环或递归)函数的inlining,同时如果编译器不确定调用哪个函数,也会拒绝函数本体的inlining。
综上,一个看似inline函数是否真的inline,取决于内置环境,主要是编译器。大多数编译器会给出警告信息如果无法将你要求的函数inline化时。
同时编译器通常不对“通过函数指针进行的调用”实施inline,这说明对于inline函数的inline与否取决于调用的实施方式,如下代码:
inline void f(){...}; //有意inline         void(*pf)()=f;  //函数指针         ...         f();   //正常调用,inline pf();      //函数指针调用,非inline

 
即使从不使用函数指针,也可能会遇到此类问题,如构造和析构函数,编译器会生成其副本并获得指针指向。
事实上构造和析构函数往往是inline的糟糕地方,如下代码:
class Base     {              public:                       ...              private:                       std::stringb1,b2;     };     class  Derived:public Base     {              public:                       Derived(){}     //空的构造函数,事实上呢?              ...              private:                       std::stringd1,d2,d3;  //派生成员1-3     };


看上去inline非常好,因为不含任何代码。但事实上不是。
当构造一个对象时,其相应基类构造函数也被调用,反之析构也如此,如果构造过程中发生异常,该对象已构造的一部分会自动销毁。
那个看起来为空的构造函数,相当于以下代码:
Derived::Derived()   //空白构造函数的观念性实现     {     Base();      //初始化Base     try{d1.std::string::string();}  //构造d1     cath(...)              {                       Base::~Base();                       throw;              }     try{d2.std::string::string();}     cath(...)              {                       d1.std::string::~string();                       Base::~Base();                       throw;              }              try{d3.std::string::string();}     cath(...)              {                       d2.std::string::~string();                       d1.std::string::~string();                       Base::~Base();                       throw;              }     }


虽然不是真正代码,但已经反应出实际行为。我们可以知道“是否将Derived构造函数inline”并非轻松。
同时inline函数不能随着程序库的升级而升级,换句话说,如果f是程序库内的一inline函数,一旦改变f,则所有用到f的客户端程序都必须重新编译,反之,如果是non-line函数,就只要重新连接就好,远比重新编译负担少。这就需要我们采取合适策略,一开始不应将任何函数声明为inline,只限定于“一定为inline”或“平淡无奇的函数”,如上面的的person::age()。
8-2法则,一个程序80%的时间用在20%的代码上,作为开发者,我们的目标是有效增进整体效率的20%的代码,然后奖它inline或尽可能瘦身。
需要记住的:1、将大多数inline限制在小型、频繁调用的函数上,会使日后调试和二进制升级更容易同时减少代码膨胀和提高程序速度。2、不要只因为函数template出现在头文件就将其声明为inline.
0 0
原创粉丝点击