条款33:明智地运用inline

1,inline函数:看起来像函数,动作起来像函数,比宏好得多,不需要蒙受函数调用带来的额外负担.
而且,编译器最佳化机制通常用来浓缩那些不含函数调用动作的代码,所以当你inline一个函数时,编译器就有能力在函数体身上执行某种最佳化.

2,inline函数背后的观念:将对函数的每一个调用行为都用函数代码取代之.
显然这么做,会增加你目标代码的大小.
inline行为造成的程序代码膨胀可能会导致病态的换页,或是降低cache的命中率.

3,特别注意的是:
inline指令就像register指令一样,只是对编译器的一种提示,而不是强制命令.
大部分编译器会拒绝将复杂的函数(内含循环或递归)inline化,所有的虚拟函数都会阻止inline的进行.
这是因为virtual意味着"等待,直到运行时期再确定调用哪一个函数."
inline意味着"在编译阶段,将调用动作以被调用函数的本体取代之"

4,理论和实际在这个问题上很可能分道扬镳:
inline函数的定义几乎总是被放在头文件中.
举个例子:
// This is file example.h
inline void f() { ... }          // definition of f

...

// This is file source1.cpp
#include "example.h"             // includes definition of f
...                              // contains calls to f

// This is file source2.cpp
#include "example.h"             // also includes definition of f
...                              // also calls f
假设f没有被inline,那么source1和source2目标文件中均含有f的函数,均未强化的,连接这两个模块时,就会造成连接错误.
因此,旧规则宣布"编译器对待一个inline失败的函数,犹如函数被声明为static似的".
由此可见,在旧的规则下,如果一个inline函数inline失败,你不仅必须在每次调用函数时付出函数调用的成本,还得承受代码体积增加的事实,因为每个调用f的编译单元都有它们自己的一份f函数吗,以及f中的每一个static变量.

5,有的情况,编译器必须为已经成功inline的函数产生一个函数体.
例如:你的程序曾经取一个inline函数的地址,编译器就必须为此函数产生一份函数实体.
如:
inline void f() {...}            // as above

void (*pf)() = f;                // pf points to f

int main()
{
  f();                           // an inline call to f

  pf();                          // a non-inline call to f
                                 // through pf
  ...
}
注:新规则之下,不论牵扯的编译单元有几个,只有一个out-of-line的f副本被产生出来.

很多时候,编译器会为constructor和destructor等函数产生out-of-line副本,从而使自己可以获得这些函数的指针.
例如:
class Base {
public:
  ...private:
  string bm1, bm2; // base members 1 and 2
};class Derived: public Base {
public:
  Derived() {}                  // Derived's ctor is
  ...                           // empty -- or is it?

private:
  string dm1, dm2, dm3;         // derived members 1-3
};
因为C++有很多保证一定会发生的事情,如"产生一个对象时,其base对象和每个data members都会被自动构造".
这些事情一定不是凭空发生的,"如何发生"是编译器实现者的权限.编译器可能会将一些代码安插到某些地方.
事实上,constructor和destructor往往不是inline的最佳候选人.
例如:
// possible implementation of Derived constructor
Derived::Derived()
{
  // allocate heap memory for this object if it's supposed
  // to be on the heap; see Item 8 for info on operator new
  if (this object is on the heap)
    this = ::operator new(sizeof(Derived));

  Base::Base();                  // initialize Base part

  dm1.string();          // construct dm1
  dm2.string();          // construct dm2
  dm3.string();          // construct dm3
}
这样一来Derived()的大小就增加了,注意string的构造函数也要增加,这样Derived将变得很大,是否将Derived()inline化,答案显而易见.

6,inline带来的另一个冲击:
inline函数无法随着程序的升级而升级.
因为一旦inline函数f被改变,所有用到f的程序都必须重新编译.
如果f不是inline,f被修改后,用户只需重新连接即可,远比重新编译的负担小.

7,inline函数中的static对象常会展现反直观的行为.
因此如果函数带有static对象,通常要避免将它声明为inline.

8,最重要的原因:大部分除错器对inline函数束手无策.

9,总结:该不该使用inline的策略
一开始,不要将任何函数声明为inline,或至少将inline范围限制在那些实在非常琐屑平凡的函数身上.
例如下面的age函数:
class Person {
public:
  int age() const { return personAge; }
  ...
private:
  int personAge;
  ...
};

根据80-20的经验法则,找出程序中占重要效率低位的函数,然后向办法将它们inline化.

你可能感兴趣的:(cache,F#)