在C++语言的设计中,内联函数的引入能够说彻底是为了性能的考虑。所以在编写对性能要求比较高的C++程序时,很是有必要仔细考量内联函数的使用。 所谓"内联",即将被调用函数的函数体代码直接地整个插入到该函数被调用处,而不是经过call语句进行。固然,编译器在真正进行"内联"时,由于考虑到被内联函数的传入参数、本身的局部变量,以及返回值的因素,不只仅只是进行简单的代码拷贝,还须要作不少细致的工做,但大体思路如此。
开发人员能够有两种方式告诉编译器须要内联哪些类成员函数,一种是在类的定义体外;一种是在类的定义体内。
(1)当在类的定义体外时,须要在该成员函数的定义前面加"inline"关键字,显式地告诉编译器该函数在调用时须要"内联"处理,如:函数
class Student { public: String GetName(); int GetAge(); void SetAge(int ag); …… private: String name; int age; …… }; inline String GetName() { return name; } inline int GetAge() { return age; } inline void SetAge(int ag) { age = ag; }
(2)当在类的定义体内且声明该成员函数时,同时提供该成员函数的实现体。此时,"inline"关键字并非必需的,如:性能
class Student { public: String GetName() { return name; } int GetAge() { return age; } void SetAge(int ag) { age = ag; } …… private: String name; int age; …… };
当普通函数(非类成员函数)须要被内联时,则只须要在函数的定义时前面加上"inline"关键字,如:优化
inline int DoSomeMagic(int a, int b) { return a * 13 + b % 4 + 3; }
由于C++是以"编译单元"为单位编译的,而一个编译单元每每大体等于一个".cpp"文件。在实际编译前,预处理器会将"#include"的各头文件的内容(可能会有递归头文件展开)完整地拷贝到cpp文件对应位置处(另外还会进行宏展开等操做)。预处理器处理后,编译真正开始。一旦C++编译器开始编译,它不会意识到其余cpp文件的存在。所以并不会参考其余cpp文件的内容信息。联想到内联的工做是由编译器完成的,且内联的意思是将被调用内联函数的函数体代码直接代替对该内联函数的调用。这也就意味着,在编译某个编译单元时,若是该编译单元会调用到某个内联函数,那么该内联函数的函数定义(即函数体)必须也包含在该编译单元内。由于编译器使用内联函数体代码替代内联函数调用时,必须知道该内联函数的函数体代码,并且不能经过参考其余编译单元信息来得到这一信息。设计
若是有多个编译单元会调用到某同一个内联函数,C++规范要求在这多个编译单元中该内联函数的定义必须是彻底一致的,这就是"ODR"(one-definition rule)原则。考虑到代码的可维护性,最好将内联函数的定义放在一个头文件中,用到该内联函数的各个编译单元只需#include该头文件便可。进一步考虑,若是该内联函数是一个类的成员函数,这个头文件正好能够是该成员函数所属类的声明所在的头文件。这样看来,类成员内联函数的两种声明能够当作是几乎同样的,虽然一个是在类外,一个在类内。可是两个都在同一个头文件中,编译器都能在#include该头文件后直接取得内联函数的函数体代码。讨论完如何声明一个内联函数,来查看编译器如何内联的。继续上面的例子,假设有个foo函数:code
#include "student.h" ... void foo() { ... Student abc; abc.SetAge(12); cout << abc.GetAge(); ... }
foo函数进入foo函数时,从其栈帧中开辟了放置abc对象的空间。进入函数体后,首先对该处空间执行Student的默认构造函数构造abc对象。而后将常数12压栈,调用abc的SetAge函数(开辟SetAge函数本身的栈帧,返回时回退销毁此栈帧)。紧跟着执行abc的GetAge函数,并将返回值压栈。最后调用cout的<<操做符操做压栈的结果,即输出。对象
#include "student.h" ... void foo() { ... Student abc; { abc.age = 12; } int tmp = abc.age; cout << tmp; ... }
这时,函数调用时的参数压栈、栈帧开辟与销毁等操做再也不须要,并且在结合这些代码后,编译器能进一步优化为以下结果:递归
#include "student.h" ... void foo() { ... cout << 12; ... }
这显然是最好的优化结果;相反,考虑原始版本。若是SetAge/GetAge没有被内联,由于非内联函数通常不会在头文件中定义,这两个函数可能在这个编译单元以外的其余编译单元中定义。即foo函数所在编译单元看不到SetAge/GetAge,不知道函数体代码信息,那么编译器传入12给SetAge,而后用GetAge输出。在这一过程当中,编译器不能确信最后GetAge的输出。由于编译这个编译单元时,不知道这两个函数的函数体代码,于是也就不能作出最终版本的优化。开发
从上述分析中,能够看到使用内联函数至少有以下两个优势。编译器
(1)减小由于函数调用引发开销,主要是参数压栈、栈帧开辟与回收,以及寄存器保存与恢复等。it
(2)内联后编译器在处理调用内联函数的函数(如上例中的foo()函数)时,由于可供分析的代码更多,所以它能作的优化更深刻完全。前一条优势对于开发人员来讲每每更显而易见一些,但每每这条优势对最终代码的优化可能贡献更大。