条款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化.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值