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

一、inline的优缺点

优点

  • 免除函数调用成本

缺点

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

二、隐式内联、显式内联

  • inline只是对编译器的一个申请,不是强制命令
  • 隐式内联只现在class的内部。例如:
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
发布了1504 篇原创文章 · 获赞 1063 · 访问量 43万+

猜你喜欢

转载自blog.csdn.net/qq_41453285/article/details/104711068