将函数指定为内联函数(inline),通常就是将它在每个调用点上“内联地”展开。。假设我们把shorterString函数定义成内联函数,则如下调用:
cout<<shorterString(s1,s2)<<endl;
将在编译过程中展开成类似于下面的形式:
cout<<(s1.size()<s2.size() ? s1 : s2)<<endl;
从而消除了shorterString函数的运行时开销。
为什么要引入内联函数
引入内联函数的主要目的是:用它替代C语言中表达式形式的宏定义来解决程序中函数调用的效率问题。在C语言中可以使用如下的宏定义:
#define ExpressionName(var1,var2) (var1+var2)*(var1-var2)
这种宏定义在形式及使用上像一个函数,但它使用预处理器实现,没有了参数压栈、代码生成等一系列操作,因此效率很高。但是也有很多缺点:
- 这种宏定义在形式上类似一个函数,但在使用它时,仅仅只是做预处理器符号表中的简单替换,因此它不能进行参数的有效检测,也就不能享受C++编译器严格类型检查的好处。
- 它的返回值也不能被强制转换为可转换的合适类型,这样它的使用就存在着一系列的隐患和局限性。
- C++中引入了类和类的访问控制,这样,如果一个操作或者说一个表达式设计类的保护乘员或私有成员,你就不能使用这种宏定义来实现(因为无法将this指针放在合适的位置)。
inline推出的目的,也正是为了取代这种表达式形式的宏定义。它消除了宏定义的缺点,同时又能很好地继承宏定义的优点。
为什么inline能很好地取代表达式形式的宏定义
有如下几种原因:
- inline定义的类的内联函数,函数的代码被放入符号表中,在使用时直接进行替换(像宏一样展开),没有了调用的开销,效率也很高。
- 类的内联函数也是一个真正的函数。编译器在调用一个内联函数时,首先会检查它的参数的类型,保证调用正确;然后进行一系列的相关检查,就像对待任何一个真正的函数一样。这样就消除了它的隐患和局限性。
- inline可以修饰某个类的成员函数,当然就可以在其中使用所在类的保护乘员及私有成员。
内联函数的使用场合
内联函数在C++类中应用最广泛的,应该是用来定义存取函数。我们定义的类中一般会把数据成员定义成私有的或者保护的,这样外界就不能直接读取我们类成员的数据了。对于私有或者保护乘员的读写就必须使用成员接口函数来进行。如果我们把这些读写成员函数定义为内联函数的话,将会获得比较好的效。
为什么不把所有的函数都定义为内联函数
内联函数是以代码膨胀(复制)为代价的,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的代码量增大,消耗更多的内存空间。
在一台内存有限的机器上,过度热衷inlining会造成程序体积太大,即使拥有虚内存,inline造成的代码膨胀亦会导致额外的换页行为(paging),降低指令高速缓存装置的击中率(instruction cache hit rate),以及伴随这些而来的效率损失。以下情况不宜使用内联函数:
- 如果函数体内的代码比较长,使用内联函数将导致内存消耗代价较高。
- 如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。
- 类的构造函数和析构函数容易让人误解成使用内联函数。要当心构造函数和析构函数可能会隐藏一些行为,如“偷偷地”执行了基类或成员对象的构造函数和析构函数。所以不要随便地将构造函数和析构函数的定义体放在类声明中。一个好的编译器将会根据函数的定义体,自动地取消不值得的内联。
内联函数和宏定义有什么区别
- 内联函数在编译时展开,宏在预编译时展开;
- 在编译的时候,内联函数可以直接被镶嵌到目标代码中,而宏只是一个简单的文本替换;
- 内联函数可以完成诸如类型检查、语句是否正确等编译功能,宏就不具有这样的功能;
- 宏不是函数,inline函数是函数;
- 宏在定义时要小心处理宏参数(一般情况是把参数用括号括起来),否则容易出现二义性。而内联函数的定义时不会出现二义性。