为什么用Inline函数?
看起来像函数,但是没有函数调用时的压栈出栈的开销。并且,我们知道编译器最优化的机制通常被设计用来浓缩那些“不含函数调用”的代码。所以当你inline了某个函数,编译器就有能力对它执行语境相关最优化。
但是并不是所有的函数都变成inline都好。我们知道,inline后,程序的目标代码是会变大的。 如果在一个内存较小的机子上运行,会导致过多的换页行为,降低高速缓存着墨的命中率,以及伴随而来的低效率。
换个角度来说,如果inline的本体很小,编译器针对“函数本体”所产出的码可能比调用所产出的码小,那确实很好,会减少目标码的量,提高高速缓存装置的命中率。
Inline函数通常一定被置于头文件内,因为大多数的建置环境在编译过程中进行inline,而为了将一个“函数调用”替换为“被调用函数的本体”,编译器必须知道那个函数长什么样子。
现在我们要理解“inline是个申请,编译器可以加以忽略”。
大部分编译器会忽略太过复杂的函数inline,而对所有的virtual函数的调用也都会使inline落空。想想这是为什么?因为virtual表示“等待,直到运行时”,而inline意味着执行前,先将调用动作替换为被调用函数的本体“,如果编译器不知道该调用哪个函数,你就很难责备它们拒绝将函数本体inline。
有时,我们虽然有意愿将某个函数inline,但是不一定成功,这还取决于你的调用方式:比如你想通过函数指针来调用。
inline void f() {...} // inline函数
void (*pf) () = f; // 定义一个指针指向函数f
...
f(); //会执行inline
pf(); //不会被inline,因为它通过函数指针来调用
想一想为什么通过函数指针不可以呢?我们知道在编译时候,我们只知道pf是一个函数指针,但是我们并不知道里面的值。直到运行时,我们才知道。是不是。所以它不能通过函数指针inline.
实际上构造函数和析构函数往往是inline的糟糕后选人。
class Base{
public:
...
private:
std::string bm1 ,bm2;
};
class Derived:public Base{
public:
Derirved(){} //真得为空吗?
...
private:
std::string dm1,dm2,dm3;
};
我们知道,在对象被创建时和被销毁时,有什么多的事情要做。比如调用基类的构造函数…,所以说在派生类中的Derived()函数并不是为空的。编译器会增加很多的东西。比如:
Derirved(){
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;
}
}
这已经准备反映了构造函数并非为空。这说明不应该全部把构造函数定义在类定义体中(因为定义在这里面,就是隐式的inline)。同样的道理也适合于析构函数。
所以说,你会明白把一个函数设置为inline是有很大的学问的。
这里面还有一个问题是:如果inline函数内部做了改动,那么所有调用此内联函数的代码都会要重新编译。而如果一个非inline函数内部做了改动,那么只需要重新进行链接就好了。
请记住:
- 将大多数inline限制在小型,被频繁调用的函数身上。这可使日后的调试过程和二进制升级更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。