前言
常规函数调用时,程序跳到函数的起始地址,并在函数结束后返回。对于内联函数,程序程序无需跳到另一个位置处执行,再跳回来,因为编译器会将内联函数的代码替换为函数调用。因此,内联函数的运行速度比普通常规稍快些。
内联函数
C++中支持内联函数,其目的是为了提高函数的执行效率,用关键字 inline 放在函数定义的前面即可将函数指定为内联函数。下面我们来看看一个例子:
#include
using namespace std;
inline int RevSign(int nr);
bool even(int nr);
bool odd(int nr);
bool even(int nr) {
if (nr == 0){
return true;
}
return odd(RevSign(nr) - 1);
}
bool odd(int nr) {
if (nr == 0){
return false;
}
return even(RevSign(nr) -1);
}
inline int RevSign(int nr) {
if (nr < 0){
return -nr;
}
return nr;
}
int main(int argc, char const *argv[])
{
cout <<"16 "<<"is even: is "<<even(16)<<endl;
cout <<"17 "<<"is odd: is "<<odd(17)<<endl;
cout <<"18 "<<"is odd: is "<<odd(18)<<endl;
return 0;
}
输出结果:
inline 函数对编译器而言必须是可见的,以便它能够在调用点内展开该函数。
注意:
inline的使用是有所限制的,inline只适合涵数体内代码简单的涵数使用,不能包含复杂的结构控制语句例如while、switch,并且不能内联函数本身不能是直接递归函数。
慎用inline
内联能提高函数的执行效率,为什么不把所有的函数都定义成内联函数?如果所有的函数都是内联函数,还用得着“内联”这个关键字吗?
内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。
如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
以下情况不宜使用内联:
(1)如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。
(2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。
类的构造函数和析构函数容易让人误解成使用内联更有效。要当心构造函数和析构
函数可能会隐藏一些行为,如“偷偷地”执行了基类或成员对象的构造函数和析构函数。所以不要随便地将构造函数和析构函数的定义体放在类声明中。
Google C++编码规范中则规定得更加明确和详细:
内联函数:
Tip: 只有当函数只有 10 行甚至更少时才将其定义为内联函数.
定义: 当函数被声明为内联函数之后, 编译器会将其内联展开, 而不是按通常的函数调用机制进行调用.
优点: 当函数体比较小的时候, 内联该函数可以令目标代码更加高效. 对于存取函数以及其它函数体比较短, 性能关键的函数, 鼓励使用内联.
缺点: 滥用内联将导致程序变慢. 内联可能使目标代码量或增或减, 这取决于内联函数的大小. 内联非常短小的存取函数通常会减少代码大小, 但内联一个相当大的函数将戏剧性的增加代码大小. 现代处理器由于更好的利用了指令缓存, 小巧的代码往往执行更快。
结论: 一个较为合理的经验准则是, 不要内联超过 10 行的函数. 谨慎对待析构函数, 析构函数往往比其表面看起来要更长, 因为有隐含的成员和基类析构函数被调用!
另一个实用的经验准则: 内联那些包含循环或 switch 语句的函数常常是得不偿失 (除非在大多数情况下, 这些循环或 switch 语句从不被执行).
有些函数即使声明为内联的也不一定会被编译器内联, 这点很重要; 比如虚函数和递归函数就不会被正常内联. 通常, 递归函数不应该声明成内联函数.(递归调用堆栈的展开并不像循环那么简单, 比如递归层数在编译时可能是未知的, 大多数编译器都不支持内联递归函数). 虚函数内联的主要原因则是想把它的函数体放在类定义内, 为了图个方便, 抑或是当作文档描述其行为, 比如精短的存取函数.
-inl.h文件:
Tip: 复杂的内联函数的定义, 应放在后缀名为 -inl.h 的头文件中.
内联函数的定义必须放在头文件中, 编译器才能在调用点内联展开定义. 然而, 实现代码理论上应该放在 .cc 文件中, 我们不希望 .h 文件中有太多实现代码, 除非在可读性和性能上有明显优势.
如果内联函数的定义比较短小, 逻辑比较简单, 实现代码放在 .h 文件里没有任何问题. 比如, 存取函数的实现理所当然都应该放在类定义内. 出于编写者和调用者的方便, 较复杂的内联函数也可以放到 .h 文件中, 如果你觉得这样会使头文件显得笨重, 也可以把它萃取到单独的 -inl.h 中. 这样把实现和类定义分离开来, 当需要时包含对应的 -inl.h 即可。
总结
内联函数并不是一个增强性能的灵丹妙药。只有当函数非常短小的时候它才能得到我们想要的效果。
inline函数仅仅是一个对编译器的建议,所以最后能否正常使用内联,看编译器的意思,编译器如果认为函数不复杂,能在调用点展开,就会真正内联,并不是说声明了内联编译时就接受内联。声明内联函数只是建议而已!
参考资料:
高质量C++/C 编程指南