inline函数很潇洒,它看起来像函数,动作也像函数,但是调用它时又不需要蒙受函数调用所带来的额外开销,达到宏的性能而使用效果却好得多
。
但是天下没有免费的午餐,inline函数在潇洒背后也有辛酸
,inline函数的原理是编译器将“对此函数的每一个调用”都以函数本地替换之,由此可能带来的负面效应是增加目标码(object code)的大小。
在一台内存有限的机器上,过度热衷inlining会造成程序体积太大,即使拥有虚拟内训,inline造成的代码膨胀亦会导致额外的换页行为(paging),降低指令高速缓存装置的命中率,以及伴随这些而来的效率损失。这一点尤其在目前炙手可热的移动开发来说很重要。
换个角度说,如果inline函数的本体很小,编译器针对“函数本体”所产出的码可能比针对“函数调用”所产出的码更小,果真如此,将函数inlining确实可能导致较小的目标码和较高的指令高速缓存命中率。
需要注意的是:inline只是对编译器的一个申请,而不是强制命令。这项申请可以隐喻提出,也可以明确提出。隐喻方式是将函数定义于class定义式内:
class Person {
public:
...
int age() const {return theAge;} //一个隐喻的inline申请,age被定义于class定义式内
...
private:
int theAge;
};
明确声明inline函数的做法则是在其定义式前加上关键字inline,例如标准的max template的实现就是:
template
inline const T& std::max(const T& a, const T& b) //一个明确的inline申请,std::max之前有关键字“inline”
{ return a > b ? b : a;}
大部分编译器拒绝将太过复杂(例如带有循环或递归)的函数inlining,而所有对virtual函数的调用(除非是最平淡无奇的)也都会使inlining落空,因为virtual意味着“等待,直到运行期才确定调用哪个函数”,而inline意味着“执行前,先将调用动作替换为被调用函数的本体”。如果编译器不知道该调用哪个函数,那就别怪它无情地拒绝将函数本体inlining了。但是也不要为此感到沮丧,幸运的是大多数编译器会在无法将你要求的函数inline化时给你一个警告信息,够哥们儿了。此外,对于通过函数指针调用的函数不会被inlining,因为编译器没有能力提出一个指针指向并不存在的函数。
还是那句话天下没有免费的午餐,inline函数带来好处的同时也会有副作用:inline函数无法随着程序库的升级而升级。换句话说:如果某个函数f是程序库内的一个inline函数,客户将”f函数本体“编进其程序中了,一旦程序库设计者决定改变f,所有用到f的客户端程序都必须重新编译。如果f是non-inline函数,且在动态库中,则升级版函数会不知不觉地被应用程序吸纳。此外,有很多调试器不支持在inline函数中设置断点,因为此时函数并不存在。
总结来了:将大不多数inlining限制在小型、被频繁调用的函数身上,这可使日后的调试过程和二进制升级更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。
阅读(2190) | 评论(1) | 转发(0) |