C++内联函数
本文基于《C++ Primer(第五版)》和网上博文的总结而成。个人水平有限,若有错误,欢迎指出。
一、为什么要用inline函数
我们编写一个小函数,以实现比较两个string形参的长度并返回长度较小的string的引用,如下(P201):
1 const string &shorterString(const string &s1,const string &s2) 2 { 3 return s1.size()<=s2.size()?s1:s2; 4 }
这样写有如下好处:
(1)阅读和理解shorterString函数的调用要比读懂等价的条件表达式容易。
(2)使用函数可以确保行为的统一,每次相关操作都能保证按照同样的方式进行。
(3)如果需要修改计算过程,显然修改函数要比找到等价表达式所有出现的地方再逐一修改更容易。
(4)函数可以被其他应用重复利用,省去了程序员重新编写的代价。
然而使用shorterString函数也存在一定的潜在缺点:调用函数一般比求等价表达式的值要慢一些。大多数机器上,一次函数调用其实包含一系列的工作;调用前要先保存寄存器,并在返回时恢复,可能需要拷贝实参,程序转向一个新的位置继续执行。
这时可以使用内联函数,内联函数通常就是将它在程序中的每个调用点上“内联地”展开,如下:
1 cout<<shorterString(s1,s2)<<endl; 2 3 /*在编译的过程中展开成类似下面的形式*/ 4 cout<<(s1.size()<s2.size()?s1:s2)<<endl;
从而消除了shorterString函数的运行时开销。这点跟宏(#define)有点像,下面会针对性的说明。
二、inline函数的使用
成为内联函数的方式有两种:一是:定义在类声明之中的成员函数将自动地成为内联函数;二是显式的则是在函数定义前加上inline关键字说明。
1、定义在类声明中
1 class A 2 { 3 public: 4 void Foo(int x,int y) {......} //自动成为内联函数 5 6 };
2、显示定义为内联函数
1 inline void Foo(int x,int y) //显示定义为内联函数 2 { 3 ...... 4 }
3、针对“定义”一词的说明
(1)定义在类中:说明仅仅是在类中声明了,然后在类外进行定义,这样是类的成员函数,但不是内联的。即成员函数不一定是内联的,必须在类中定义。这里要明白声明和定义的区别。编译器在调用点内联展开函数的代码时,必须能够找到 inline 函数的定义才能将调用函数替换为函数代码,而对于在头文件中仅有函数声明是不够的。
(2)针对显示定义内联函数,特别值得注意的是:C++ inline函数是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。什么意思了?个人理解是:不能将关键词inline 仅和函数的声明放在一起,而函数的定义之前不放。如:
1 inline void Foo(int x,int y); //仅和函数申明放在一块 2 void Foo(int x,int y) 3 { 4 ...... 5 }
上述Foo函数不是内联函数,正确的打开方式如下:
1 void Foo(int x,int y); 2 inline void Foo(int x,int y) //和函数定义放在一块 3 { 4 ...... 5 }
上述代码第一行函数申明之前也可以加上inline关键字,但在《高质量C/C++编程》一文中这样写道:inline 不应该出现在函数的声明中。这个细节虽然不会影响函数的功能,但是体现了高质量C++/C 程序设计风格的一个基本原则:声明与定义不可混为一谈,用户没有必要、也不应该知道函数是否需要内联。
三、inline函数的注意事项
inline的原理,是用空间换取时间的做法,是以代码膨胀(拷贝)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。所以,如果函数体代码过长或者函数体重有循环语句,if语句或switch语句或递归时,不宜用内联。另外,实际上,inline在实现的时候是对编译器的一种请求,因此编译器完全有权利取消一个函数的内联请求,一个好的编译器能够根据函数的定义体,自动取消不值得的内联,或者自动地内联的一些没有inline请求的函数。
Tips:
(1)只有当函数只有10行甚至更少时才将其定义为内联函数。
(2)谨慎对待析构函数, 析构函数往往比其表面看起来要更长, 因为有隐含的成员和基类析构函数被调用!
(3)有些函数即使声明为内联的也不一定会被编译器内联, 这点很重要; 比如虚函数和递归函数就不会被正常内联. 通常, 递归函数不应该声明成内联函数.(递归调用堆栈的展开并不像循环那么简单, 比如递归层数在编译时可能是未知的, 大多数编译器都不支持内联递归函数). 虚函数内联的主要原因则是想把它的函数体放在类定义内, 为了图个方便, 抑或是当作文档描述其行为, 比如精短的存取函数.
四、和宏的区别
(1)使用宏代码最大的缺点是容易出错,预处理器在拷贝宏代码时常常产生意想不到的边际效应。见博文
(2)宏的另一个缺点就是不可调试,但是内联函数时可以调试的。这里说的可调试不是说内联函数展开以后还能调试,而是在程序的调试(Debug)版本里它根本没有真正内联,编译器像普通函数那样为它生成调试信息的可执行代码。在程序的发行(Release)版本里,编译器才会实施真正的内联。
(3)对C++而言,使用宏代码还有另一个确定:无法操作类的私有数据成员。
Ref:
http://blog.csdn.net/coder_xia/article/details/6723387