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

本文详细解析了inline函数的概念、使用方法及其对编译器的影响。包括如何在类定义中隐式或明确申请inline,template函数与inline的关系,构造函数的inlining考量,以及inline函数的升级问题。同时强调了合理使用inline函数的重要性,以优化代码效率与维护性。
摘要由CSDN通过智能技术生成

(一)

inline函数,可以调用它们而又不需蒙受函数调用所招致的额外开销。

inline函数背后的整体观念是,将“对此函数的每一个调用”都已函数本体替换之,这样做可能增加你的目标码(object code)大小。在内存有限的机器上,过度inline会造成程序体积太大,导致换页行为,降低缓存的命中率等一些带来效率损失的行为。如果inline函数的本体很小,编译器针对“函数本体”所产生的码可能比针对“函数调用”所产出的码更小。将函数inline可以导致更小的目标码,从而提高效率。


(二)

inline只是对编译器的一个申请,不是强制命令。这种申请可以隐喻提出也可以明确提出。

(1)隐喻提出(隐喻方式是将函数定义于class定义式内):

class Person { 
public: 
    ... 
    int age() const {return theAge;}//一个隐喻的inline申请 
    ... 
private: 
    int theAge; 
};
(2)明确提出( 明确申请inline函数的做法是在其定义式前加上关键字inline):

template<typename T> 
inline const T& std::max(const T& a, const T& b) { 
    return a < b? b: a; 
}


(三)

(1)如果我们正在写一个template而我们认为素有根据此template具现出来的函数都应该inlined,那么请将此template声明为inline;

       如果template没有理由要求它所具现的每个函数都是inlined,就应该避免将这个template声明为inline(不论显式还是隐式)。inline需要成本。

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

(3)有时,虽然编译器有意愿inlining某个函数,但还是可能为该函数生成一个函数本体。例如,如果程序要取某个inline函数的地址因为编译器通常必须为此函数生成一个outlined函数本体(毕竟编译器没有能力提出一个指针指向并不存在的函数),所以编译器通常不对“通过函数指针而进行的调用”实施inlining。

inline void f(){…} //假设编译器有意愿inline“对f的调用”
void (*pf)() = f;
f();//这个调用将被inlined,因为是一个正常调用
pf();//这个调用或许不被inlined,因为通过指针达成

(四)

class base { 
public: 
    ... 
private: 
    std::string bm1, bm2; 
};

class Derived : public Base { 
public: 
    Derived(){ }  //Derived 构造函数是空的 是吗? 
    ... 
private: 
    std::string dm1, dm2, dm3; 
};

这个构造函数看起来是inlining的绝佳候选人,因为他根本不含任何代码,但是

C++对于“对象被创建和被销毁时发生什么事”做了各式各样的保证。编译器为稍早说的那个表面上看起来是空的Derived构造函数所产生的代码,相当于以下所列:

Derived::Derived() { 
   Base::Base(); 
    try{dm1.std::string::string();} 
    catch(...){ 
        Base::~Base(); 
        throw; 
    } 
    try{dm2.std::string::string();} 
    catch(...){ 
        dm1.std::string::~string(); 
        Base::~Base(); 
        throw; 
    } 
    try{dm3.std::string::string();} 
    catch(...){ 
        dm2.std::string::~string(); 
        dm1.std::string::~string(); 
        Base::~Base(); 
        throw; 
    } 
}
这段代码并不能代表编译器真正制造出来的代码,因为真正的编译器会以更精致复杂的做法来处理异常. 尽管如此,这已能准确反映Derived的空白构造函数必须提供的行为。 Derived构造函数至少一定会陆续调用其成员变量和baseclass两者的构造函数,而那些调用(它们自身也可能被inlined)会影响编译器是否对此空白函数inlining。

(五)

程序库设计者必须评估"将函数声明为inline"的冲击:inline函数无法随着程序库的升级而升级。

客户将“f函数本体”编进其程序中,一旦程序库设计者决定改变f,所有用到f的客户端程序都必须重新编译。然而若f是non-inline函数,客户端只要重新连接就好了,如果是程序库采用动态链接,升级后的函数甚至可以不知不觉的被应用程序吸纳。

请记住:

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

(2) 不要因为function templates出现在头文件,就将它们声明为inline。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值