Inline函数,多棒的点子!它们看起来像函数,动作像函数,比宏好得多(见条款2),可以调用它们又不需要蒙受函数调用所招致的额外开销。你还能要求更多吗?
你实际获得的比想到的还多,因为“免除函数调用成本”只是故事的一部分而已。编译器最优化机制通常被设计用来浓缩那些“不含函数调用”的代码,所以当你inline某个函数,或许编译器就因此有能力对它(函数本体)执行语境相关最优化。
然而编写程序就像现实生活一样,没有白吃的午餐。inline函数也不例外。inline函数背后的整体观念是,将“对此函数的每一个调用”都以函数本体替换之。这样做可能增加你的目标码(object code)大小。在一台内存有限的机器上,过度热衷inlining会造成程序体积太大(对可用空间而言)。即使拥有虚内存,inline造成的代码膨胀亦会导致额外的换页行为(paging),降低指令高速缓存装置的击中率,以及伴随这些而来的效率损失。
换个角度说,如果inline函数本体很小,编译器针对“函数本体”所产出的码可能比针对“函数调用”所产出的码更小。果真如此,将函数inlining确实可能导致较小的目标码(object code)和较高的指令高速缓存装置击中率!
记住,inline只是对编译器的一个申请,不是强制命令。这项申请可以隐喻提出,也可以明确提出。隐喻方式是将函数定义于class定义式内:
class Person {
public:
...
int age() const { return theAge; } // 一个隐喻的inline申请:age被定义于class定义式内。
...
private:
int theAge;
};
这样的函数通常是成员函数,但条款46说friend函数也可被定义于class内,如果真是那样,它们也是被隐喻声明为inline。
明确声明inline做法则是在其定义式前加上关键字inline。例如标准的max template(来自<algorithm>)往往这样实现出来:
template<typename T>
inline const T& std::max(const T& a, const T& b) // 明确申请inline:std::max前有关键字“inline”
{
return a < b ? b : a;
}
“max是个template”带出了一项观察结果:我们发现inline函数和templates两者通常都被定义于头文件内。这使得某些程序员以为function templates一定必须是inline。这个结论不但无效而且可能有害,值得深入看一看。
Inline函数通常一定被置于头文件内,因为大多数建置环境(build environments)在编译过程中进行inlining,而为了将一个“函数调用”替换为“被调用函数的本体”,编译器必须知道那个函数长什么样子。某些建置环境可以在连接期完成inlining,少量建置环境如基于.NET CLI(Common Language infrastructure:公共语言基础设施)的托管环境竟可在运行期完成inlining。然而这样的的环境毕竟是例外,不是通例。Inlining在大多数C++程序中是编译器行为。
Templates通常也被置于头文件内,因为它一旦被使用,编译器为了将它具现化,需要知道它长什么样子。
Templates的具现化于inlining无关。如果你再写一个templates而你认为所有根据此template具现出来的函数都应该inlined,请将此template声明为inline;这就是上述std::max代码所为。但如果template没有理由要求它所具现化的每一个函数都是inlined,就应避免将template声明为inline。
大部分编译器拒绝将太过复杂(例如带有循环或递归)的函数inlining,而所有对virtual函数的调用也都会使inlining落空。这不该令你惊讶,因为virtual意味“等待,直到运行期才确定调用哪个函数”,而inline意味“执行前,先将调用动作替换为被调用函数的本体”。如果编译器不知道该调用哪个函数,你就很难责备它们拒绝将函数本体inlining。
这些叙述整合起来的意思就是:一个表面上看似inline的函数是否真是inline,取决于你的建置环境,主要取决于编译器。幸运的是大多数编译器提供了一个诊断级别:如果它们无法将你要求的函数inline化,会给你一个警告信息(见条款53)。
这是我们决定哪个函数该被声明为inline,而哪些函数不该时,掌握一个合乎逻辑的策略。一开始先不要将任何函数声明为inline,或至少将inlining施行范围局限在那些“一定成为inline”(见条款46)或“十分平淡无奇”的函数身上。慎重使用inline。不要忘记80-20经验法则:平均而言一个程序往往将80%的执行时间花费在20%的代码上头。这是一个重要的法则,因为它提醒你,作为一个软件开发者,你的目标是找出这可以有效增进程序整体效率的20%代码,然后将它inline或竭尽所能地将它瘦身,但除非你选对目标,否则一切都是虚功。
请记住
- 将大多数inlining限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。
- 不要只因为function templates出现在头文件,就将它们声明为inline。