读书笔记《Effective C++》条款30:透彻了解inlining的里里外外

来源:互联网 发布:lol出现一个网络问题 编辑:程序博客网 时间:2024/04/30 18:03

inline函数,可以调用它们又不需要函数调用所招致的额外开销。inline函数背后的整体观念是,将“对此函数的每一个调用”都以函数本体替换之。这样做可能增加目标码大小,引发代码膨胀。

换个角度说,如果inline函数的本体很小,编译器对“函数本体”所产出的码可能比针对“函数调用”所产出的码更小。

inline可以以隐喻方式将函数定义于class定义式内,任何在类内部定义的函数自动地成为inline函数:

class Person {public:        int age() const//一个隐喻的inline,定义于class定义内        {                return theAge;        }private:        int theAge;};

明确声明inline函数的做法则是在其定义式前加上关键字inline。

class Person {public:int age() const;private:int theAge;};inline int Person::age() const{return theAge;}
inline函数应该在头文件中定义,这一点不同于其他函数。inline函数的定义对于编译器而言必须是可见的,以便编译器能够在调用点内联展开该函数的代码。此时,仅有函数原形是不够的。内联函数可能在程序中定义不止一次,只要内联函数的定义在某个源文件中只出现一次,而且在所有源文件中,其定义必须是完全相同的。把内联函数的定义放在都文件中,可以确保在调用函数时所使用的定义是相同的,并且保证在调用点该函数的定义对编译器可见。在头文件中加入或修改内联函数时,使用了该头文件的所有源文件都必须重新编译。

inline在大多数C++程序中是编译期行为。

inline是个申请,编译器可加以忽略。

大部分编译器拒绝将太过复杂(例如带有循环或递归)的函数inline,而所有对virtual函数的调用也都会使inline落空,因为virtual意味“等待,直到运行期才确定调用哪个函数”,而inline意味“执行前,先将调用动作替换为被调用函数的本体”。如果编译器不知道该调用哪个函数,你就很难责备它们拒绝将函数本体inline。

总结来说,一个表明上看似inline的函数是否真是inline,取决于你的建置环境,主要取决于编译器。幸运的是大多数编译器提供了一个诊断级别:如果它们无法将你要求的函数inline化,会给你一个警告信息。

此外,编译器通常不对“通过函数指针而进行的调用”实施inline,这意味对inline函数的调用有可能被inline,也可能不被inline,取决于该调用的实施方式:

inline void f() {}//假设编译器有意愿inline“对f的调用”void (*pf)() = f;f();//这个调用将被inline,因为它是一个正确调用pf();//这个调用或许不被inline,因为它通过函数指针达成
在VS2013上测试,pf()可以编译通过。推断:如果要取得一个inline函数的地址,编译器就必须为此函数产生一个函数实体,无论如何,编译器无法交出一个“不存在函数”的指针。

实际上构造函数和析构函数往往是inline的糟糕候选人。构造函数和析构函数虽然“看”似简单,但编译器会在背后做很多事情,比如一个空的构造函数里面会由编译器写上对所有成员的初始化,如果将之inline,将会导致大批量的代码复制,所以不对构造函数和析构函数inline为好。

此外,程序库设计者必须评估“将函数声明为inline”的冲击:inline函数无法随着程序库的升级而升级。换句话说如果f是程序库内的一个inline函数,客户将“f函数本体”编进其程序中,一旦程序库设计者决定改变f,所有用到f的客户端程序都必须重新编译。这往往是大家不愿意见到的,这点和define类似。然而如果f是non-inline函数,一旦它有任何修改,客户端只需要重新连接就好,远比重新编译的负担少得多。如果程序库采用动态连接,升级版函数甚至可以不知不觉地被应用程序吸纳。

另外,大部分调试器面对inline函数都束手无策。


要点:

1.将大多数inline限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二级制升级更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。

2.不要只因为function template出现在头文件,就将它声明为inline。

阅读全文
0 0
原创粉丝点击