Effective c++ 条款30:透彻了解inlining的里里外外

内联函数,它们看起来像函数,动作像函数,比宏好的多,可以调用它们又不需蒙受函数调用所招致的额外开销,你还能要求更多吗?

1、内联函数的益处

你实际获得的比想到的还多。编译器最优化机制通常被设计用来浓缩那些“不含函数调用”的代码。所以当你inline某个函数,或许编译器就因此有能力对它执行语境相关最优化。

2、使用时可能带来的后果

然而内联函数背后的整体观念是,将“对此函数的每一个调用”都以函数本体替换它,这样做可能会增加你的目标码大小,在一台内存有限的机器上,过度热衷内联会造成程序体积过大,即使拥有虚内存,内联造成的代码膨胀也会导致额外的换页行为,降低指令高速缓存装置的击中率,以及伴随这些而来的效率损失。但是如果内联函数的本体很小,则前面说到的效果可能将是相反:更小的目标吗和更高的击中率!

3、inline只是对编译器的一个申请

inline不是强制命令,这项申请可以隐喻提出,也可以明确提出。
隐喻方式是将函数定义于class定义式内,这样的函数通常是成员函数,若friend函数也被定义于class内,则它们也是被隐式声明的内联函数。
明确声明内联函数的做法则是在其定义式前加上关键字inline。
Inline函数通常被置于头文件内,因为大多数build environments在编译过程中进行内联,而为了将一个“函数调用”替换为“被调用函数的本体”,编译器必须知道这个函数长什么样。
templates通常也被置于头文件内,因为它一旦被使用,编译器为了将它具现化,需要知道它长什么样子。
templates的具现化和inlining无关。
如果你正在写一个template而你认为所有根据此template具现出来的函数都应该inlined,请将template声明为inline,否则就避免将这个template声明为inline,因为inline可以引发代码膨胀。

4、什么样的函数可以内联

大部分变压器拒绝将太过复杂(例如带有循环或递归)的函数内联,而对所有virtual函数的调用也都会使inlining落空(除非是最平淡无奇的)。因为virtual意味着‘’等待,直到运行期才确定调用哪个函数”,这与我们前面所讨论的明显矛盾。
有时候虽然编译器有意愿内联某个函数,可能还是会为该函数生成一个函数本体。例如程序要取某个内联函数的地址,编译器必须为此函数生成一个outlined函数本体,才能指向已存在的函数。编译器也通常不对“通过函数指针调用”实施内联。
有时候编译器会生成构造函数和析构函数的outlined副本,如此一来它们就可以获得指针指向那些函数,在array内部元素的构造和析构过程中使用。
实际上构造函数和析构函数往往是内联的糟糕候选人,例如,当你使用new和delete,对应的构造函数和析构函数会被调用;如果Base class的构造函数被内联,所有替换“Base构造函数调用”而插入的代码也都会被插入到派生构造函数调用内。

5、程序库设计者必须评估“将函数声明为inline”的冲击

inline函数无法随着程序库的升级而升级。如果f是程序库内的一个inline函数,客户将f函数本体编进其程序中,一旦程序库设计者决定改变f,所有用到f的客户端程序都必须重新编译。而如果f只是non-inlined函数,客户端只需重新连接就好。

猜你喜欢

转载自blog.csdn.net/unirrrrr/article/details/81368600