学过基本程序课的同学都知道,inline是内联的关键字,它可以建议编译器将函数的每一个调用都用函数本体替换。这是一种以空间换时间的做法。把每一次调用都用本体替换,无疑会使代码膨胀,但可以节省函数调用的成本,因为函数调用需要将之前的参数以堆栈的形式保存起来,调用结束后又要从堆栈中恢复那些参数。
但注意inline只是对编译器的一个建议,编译器并不表示一定会采纳,比如当一个函数内部包含对自身的递归调用时,inline就会被编译器所忽略。对于虚函数的inline,编译器也会将之忽略掉,因为内联(代码展开)发生在编译期,而虚函数的行为是在运行期决定的,所以编译器忽略掉对虚函数的inline。对于函数指针,当一个函数指针指向一个inline函数的时候,通过函数指针的调用也有可能不会被编译器处理成内联。
另一方面,即使有些函数没有inline关键字,编译器也会将之内联,用本体替换调用代码,比如直接写在class内部的成员函数,如下:
1 class Person 2 { 3 private: 4 int age; 5 public: 6 int getAge() const 7 { 8 return age; 9 } 10 void setAge(const int o_age); 11 }; 12 void Person::setAge(const int o_age) 13 { 14 age = o_age; 15 }
这里getAge()尽管没有inline关键字,但因为是直接写在class里面的,所以编译器将之处理成内联的;setAge()是在类内声明、类外定义的,编译器就不会将之处理成内联的了。
构造函数和析构函数虽然“看”似简单,但编译器会在背后做很多事情,比如一个空的构造函数里面会由编译器写上对所有成员函数的初始化,如果将之inline,将会导致大批量的代码复制,所以不对构造函数和析构函数inline为好。
要慎用inline,是因为一旦编译器真的将之inline了,那么这个inline函数一旦被修改,整个程序都需要重新编译,而如果这个函数不是inline的,那么只要重新连接就好。另外,一些调试器对inline函数的支持也是有限的。
作者认为,“一开始先不要将任何函数声明为inline”,经测试,确实发现某个函数的inline要比不对之inline的性能提升很多,才对之inline。在大多数情况下,inline并不是程序的瓶颈,真正的精力应该放在改善一些算法的修缮,以及反复调用的代码研究上,它们往往才是耗时的瓶颈所在。
最后总结一下:
1. 将大多数inlining限制在小型、被频繁调用的函数身上
2. 不要只因为function templates出现在头文件,就将它们声明为inline(这个内容知道就行了,我在读书笔记的正文中没有说)