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

一、inline的优缺点

优点

  • 免除函数调用成本

缺点

  • 以函数本体代替函数调用,因此目标码增大
  • 在一台内存优先的机器上,过度使用inline会造成程序体积太大
  • 即使拥有虚内存,inline造成的代码膨胀也会造成额外的换页行为,降低指令高速缓存装置的集中率,以及伴随效率的损失

二、隐式内联、显式内联

  • inline只是对编译器的一个申请,不是强制命令
  • 隐式内联:当成员函数定义在类的内部时,这个函数是隐式inline的(隐式内联只有这一种情况)。例如:
class Person {

public:

    //隐式内联(编译器自动申请),这个函数不仅在类中声明,还在类中进行了定义

    int age()const { return theAge; }

private:

    int theAge;

};
  • 显式内联:我们也可以通过inline关键字显式的指出一个函数作为内联函数。例如:
template<typename T>

inline const T& std::max(const T& a, const T& b)

{

    return a < b ? b : a;

}

三、模板与内联

  • inline函数通常被置于头文件内,因为大多数建置环境在编译过程中进行inlining,需要知道内联函数长什么样子。inlining在大多数C++程序中是编译期的行为(但是也有少数情况是在运行期链接期完成inlining)
  • template模板通常也被置于头文件内,因此它一旦被使用,编译器为了将其实例化,也需要知道它长什么样子
  • template的具体化与inlining无关:
    • 如果你写的模板认为具体实现处的函数应该是inlining的,那么就将template声明为inline
    • 如果你写的代码没有理由应该是inlining的,那么就将不要将template声明为inline(因为可能会产生代码膨胀)

四、编译器拒绝内联的情况

  • 即使你将函数声明为inline的,但是在有些情况下编译器会拒绝将函数作为inlining。例如:
    • 太过复杂的函数:例如带有循环或递归
    • 对virtual函数的调用(除非是最平淡无奇的):因为virtual意为“等待”,直到运行期才确定调用哪个函数,而inline意味着在编译期就能够确定调用函数本体。因此virtual函数将被编译器拒接生成为inline的
  • 总结:
    • 一个表面看似inline的函数,或者显式使用inline声明的函数,到底是不是一个内联函数,取决于你的环境与编译器
    • 大多数编译器提供了一个诊断级别:如果无法为函数inline化,会给出一个警告

构造函数与析构函数有时也不是inlining的

  • 现在有下面的一个类继承体系
class Base{

public:

//...

private:

    std::string bm1, bm2;

};


class Derived :public Base {

public:

        Derived() {} //构造函数为空

private:

    std::string dm1, dm2, dm3;

};
  • 上面的Derived构造函数为空,此时你可能会认为Derived的构造函数时inlining的,但是事实上不是这样的
  • 我们知道C++的构造函数与析构函数有如下部分规则:
    • 如果是派生类,那么在构造自己之前还需要执行基类的构造函数,析构函数类似
    • 如果没有在构造函数内为类的数据成员做初始化,那么编译器会自动为类的数据成员做初始化(这些初始化代码是编译器自己添加的)
  • 例如上面的Derived的构造函数虽然为空,但是其有3个数据成员,基类有2个数据成员。下面是伪代码,编译器会自动为这些数据成员进行初始化:
//伪代码

Derived::Derived()

{

    //下面是编译器为空的Derived构造函数添加的代码

    Base::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;

}

}

 

五、总结

  • 将大多数inlining限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化
  • 不要只因为function templates出现在头文件,就将它们声明为private
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值