C++实现之——inline

inline函数的本质是对函数的每一个调用都以函数本体替换它,它的优点主要有:

1)没有一般函数的调用成本;

2)方便编译器进行优化:编译器最优化机制通常被设计用来浓缩那些“不被函数调用”的代码,所以当你inline某个函数,编译器就有能力对函数本体执行语境相关最优化,大部分编译器不会对着一个“outlined函数调用”动作执行如此之最优化。

然而,对于inline,还有下面几个问题是需要注意的:

1)由于inline真正的操作时用函数本体代替调用的代码,因此容易造成程序体积过大,这样一来,代码膨胀可能造成额外的换页行为,降低指令高速缓存装置的击中率,引发效率损失;反之,如果

函数的本体很小,编译器针对“函数本体”所产生的代码可能比针对“函数调用”所产出的代码更小。这样的话,将函数inline确实可能导致较小的目标码和较高的指令高速缓存装置击中率;

2)另外,inline只是对编译器的一个申请,不是强制命令,大多数编译器拒绝将太过复杂的函数inlining,而所有对virtual函数的调用也都会使inline落空,这是因为,virtual意味着"等待,直到运行期才确定调用哪个函数",而inline意味“执行前,先将调用动作替换为被调用函数的本体”,因此对于virtual函数,可以这样理解,因为编译器不知道它要调用哪个函数,因此拒绝将它inline;换句话将,即使显示的inline某个函数,该函数是否真的是inlined,还要取决于编译器,通常情况下,如果编译器无法将你要求的函数inline化,会给出警告信息;

3)有些时候虽然编译器有意愿inline某个函数,还是可能为该函数生成一个函数本体。如,如果程序要取某个inline函数的地址,编译器通常必须为此函数生成一个outlined函数本体,毕竟编译器没有能力提出一个针对并不存在的函数的指针;另一方面,编译器通常不对“通过函数指针而进行的调用”实施inline,如下面的代码:

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

4) 构造函数和析构函数通常是inline的糟糕候选人,以下面的代码进行分析:

 1 class Base
 2 {
 3 public:
 4 ...
 5 private:
 6      string bm1, bm2;
 7 };
 8 
 9 class Derived::public Base
10 {
11 public:
12      Derived() { }
13      ...
14 private:
15      string dm1, dm2,dm3;
16 };

第12行中的构造函数看上去是个空白,因而适合inline,但事实上,在编译期间,编译器对其进行处理并插入了许多复杂精致的处理代码,至少包含如下代码:

Derived::Derived()
{
     Base::Base();
     try { dm1.string();}
     catch ( ... )
     {
          Base::~Base();
          throw;
     }

     try { dm2.string(); }
     catch ( ... )
     {
          dm1.~string();
          Base::~Base();
          throw;
     }

     try { dm3.string(); }
     catch ( ... )
     {
          dm2.~string();
          dm1.~string();
          Base::~Base();
          throw;
     }


}

因此,是否将构造函数或析构函数inline需要慎重考虑。

5)对程序库设计者来说,还要注意,inline函数无法随着程序库的升级而升级,换句话说,如果f是程序库内的一个inline函数,客户将“f函数本体”编进程序中,一旦程序库设计者决定改变f,所有用到f的客户端程序都必须重新编译,而如果f不是inline函数,一旦它有任何修改,客户端只需重新连接就好,远比重新编译的负担少得多。如果程序库采取动态链接,升级版函数甚至可不知不觉地被应用程序吸纳。

6)此外,大部分调试器面对inline函数都束手无策,因为不能在并不存在的函数内设立断点。

 

总结,应尽量将inline限制在小型,被频繁使用的函数上,这可使日后的调试过程和二进制升级更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。

 

以上整理自Effective C++中文版第三版 case 30.

 

posted on 2013-06-03 22:26  Sophia-呵呵小猪  阅读(287)  评论(0编辑  收藏  举报