天天看点

C++基础——inline 的使用inline

inline

在c/c++中,为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了inline修饰符,表示为内联函数,栈空间就是指放置程序的局部数据(也就是函数内数据)的内存空间。在系统下,栈空间是有限的,假如频繁大量的使用就会造成因栈空间不足而导致程序出错的问题,如,函数的死循环递归调用的最终结果就是导致栈内存空间枯竭。

inline函数不需要承受函数调用所致的额外开销,inline函数背后的整体观念是“对函数的每一次调用都要函数体本身替换之”,这样能提高函数效率,但是可能增加目标码大小,在一台内存有限的机器上过渡热衷于inlining会造成程序体积太大;如果inline函数的体积很小,编译器对“函数本体”所产出的码可能比针对“函数调用”所产出的码更小,这种情况将函数inlining可以生成较小的目标码和较高的指令高速缓存装置命中率。

inline 只是对编译器的一个申请,不是强制命令;同时并不是所有的函数都能定义成内联函数!如果函数体内的代码比较长,使用内联将导致内存消耗代价比较高;如果函数体内出现循环或者递归,那么执行函数体内代码的时间要比函数调用的开销大。

inline的使用

1. 隐式申请inline

class Person{
public:
    ...
    //隐式的inline申请:age被定义在class定义式内
    int age() const { return theAge;  }
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;
}
           

3. 编译器对 inline 函数的处理

  • 将 inline 函数体复制到 inline 函数调用点处;
  • 为所用 inline 函数中的局部变量分配内存空间;
  • 将 inline 函数的的输入参数和返回值映射到调用方法的局部变量空间中;
  • 如果 inline 函数有多个返回点,将其转变为 inline 函数代码块末尾的分支(使用 GOTO)。

4. 编译器不会对函数指针inlining

inline void f() { ... }//编译器对f 函数 inline

void (*pf)() = f;

f();//这个调用会被inline,是一个正常的调用

pf();//这个调用或许不会inlined,因为通过指针调用
 
           

5. 虚函数(virtual)与inline?

  • 虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联。
  • 内联是在编译时建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。
  • inline virtual

     唯一可以内联的时候是:编译器知道所调用的对象是哪个类,这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。

6. 构造函数、析构函数与inline

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

由于C++对于“对象被创建和销毁时发生什么事情做了各种保证”;使用new动态创建的对象背鳍构造函数自动初始化;使用delete时对应的析构函数被调用;如果有异常在构造期间被抛出,该对象已构建好的那一部分会被自动销毁掉;所以编译器会为上面的构造函数产生类似如下代码:

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;
    }
    
}
           

通过上述代码可以发现,如果Base构造是inline, Derived也是inline的话,Base的构造函数代码将被插入Derived构造中,意味着Derived中执行多个string构造;而在调用Derived的地方也将全部替换这些代码, 随着调用的次数越多目标代码将越庞大;是否使用inline ,取决于此函数是否简单(慎用inline)。

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

以上参考:

  • Effective C++
  • https://github.com/Light-City/CPlusPlusThings