透彻了解inling的里里外外
inline函数,看起来像函数,动作像函数,比宏好用得多,可以调用它们又不需要受函数调用所招致的额外开销。
inline函数背后的整体观念是,将“对此函数的每一个调用"都以函数本体替代之。但是这样可能会增加你的目标码(object code)。过度热衷inlining会造成程序体积太大。
inline只是对编译器的一个申请,不是强制命令。这项申请可以隐喻提出,也可以明确提出。
- 隐喻方式将函数定义于class定义式内:
class Person
{
public:
...
int age() const { return theAge; } // 一个隐喻的inline申请
...
private:
int theAge;
}
- 明确申明inline函数的做法则是在其定义式前加上关键字inline。
template<typename T>
inline const T& std::max(const T& a, const T& b)
{
return a < b ? b : a;
}
大部分编译器拒绝将太过复杂的(带有循环或递归)函数inlining,而所有对virtual函数的调用也都会是linining落空。因为virtual因为"等待,知道运行期才确定调用哪个函数",而inline意味"执行前,先将调用动作替换成为被调用函数的本体。"
一个表面上看似inline的函数是否真的inline取决于运行环境,主要取决于编译器,幸运的是大多数表一起提供了一个诊断级别:
如果它们无法将你要求的函数inline化,会给出一个警告。
有时候虽然编译器有意愿inlining某个函数,还是可能为该函数生成一个函数本体。例如:
如果程序要去取某个inline函数的地址,编译器通常必须为此函数生成一个outlined函数本体。毕竟编译器没有能力提出一个指针指向并不存在的函数。
inline函数无法随着程序库的升级而升级。如果f是程序库内的一个inline函数,客户将"f函数本体"编进其程序中,一旦程序库设计这决定改变f,所有用到f的客户端程序都必须重新编译。如果f是non-inline函数,一旦又任何修改,客户端只需要重新连接就好,远比重新编译的负担少。如果程序采用动态连接,升级版函数甚至可以不知不觉地被应用程序吸纳。
大部分调试器面对inline函数都束手无策。因为没办法在一个并不存在的函数内设立断点。虽然某些环境中勉强支持对inline函数的调试,其他许多环境仅仅只能"在调试版程序中禁用发生inlining"。
请记住:
- 将大多数inlining限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。
- 不要只以为funciton template出现在头文件,就将它们声明为inline。