一、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