1.inline函数:
本质:对inline函数的调用都以函数本体替换;
inline函数通常一定被置于头文件内,因为大多数环境在编译期进行inlining,将“函数调用”替换为“被调用函数的本体”;
优点:
动作像函数,可以调用而避免函数调用的额外开销;
编译器有能力对inline函数执行语境相关最优化(编译器的最优化机制被设计用来浓缩“不含函数调用”的代码);
缺点:
可能增加目标码的大小:造成代码膨胀会导致额外的换页行为,降低指令高速缓存的命中率;(而当inline函数的本体很小时,编译器针对“函数本体”产生的码可能比针对“函数调用”产生的码小,这样,将函数inline导致较小的目标码和较高的指令高速缓存的命中率)
2.inline函数的声明:
1)隐式声明:将函数定义于class定义式内:
如下代码中,类中的函数age被隐式声明为inline:
class Person {
public:
int age() const {
return theAge;
}
private:
int theAge;
};
2)显式声明:函数定义式前加关键字inline
在标准的max template(<algotirhm>)往往这样实现:
template<typename T>
inline const T& std::max(const T& a, const T& b) {
return a < b ? b : a;
}
3. inline是一个申请,编译器可以忽略:
大多数编译器提供诊断级别:如果编译器无法将要求的函数inline化,会给出警告信息;
编译器拒绝inline的场景:
1)virtual函数的inline:
virtual意味着“直到运行期才确定调用的函数”;
inline意味着“执行前(编译期),先将调用动作替换为被调用的函数本体”;
2)通过函数指针而进行的调用:
编译器没有能力让一个指针指向并不存在的函数,所以通过函数指针实施的调用可能不被inlined;
inline void f() {
cout << "Hello" << endl;
}
void(*pf)() = &f;
int main()
{
f(); //这个调用将被inlined,因为是一个正常的调用
pf(); //这个调用或许不被inlined,因为通过函数指针调用
return 0;
}
3)构造函数和析构函数:
class Base {
public:
Base(){}
private:
std::string bm1, bm2; //base类的两个成员
};
class Derived : public Base {
Derived(){} //空白的构造函数
private:
std::string dm1, dm2, dm3; //Derived类的三个成员
空白的构造函数中,似乎是inlining的候选人,但这个构造函数中实际执行的内容并非我们看到的“空白”。
- 当使用new,动态创建的对象被其构造函数自动初始化;使用delete,对应的析构函数被调用。
- 当创建一个对象,该对象的每一个base class及每一个成员变量都会被自动构造;当销毁一个对象,反向程序的析构行为也自动发生。
- 如果有个异常在对象的构造期间被抛出,该对象已经构造好的那一部分就会被自动销毁。
基于上述三点,可以分析在看似“空白”的Derived构造函数中真正执行的内容:
Derived::Derived() {
Base::Base(); //构造基类部分的成员变量
try {
dm1.std::string::string(); //试图构造dm1
}
catch () { //如果抛出异常,就销毁Base class部分,并传播该异常
Base::~Base();
throw;
}
try {
dm2.std::string::string(); //试图构造dm2
}
catch () {
dm1.std::string::~string(); //如果抛出异常,就销毁dm1和Base class部分,并传播该异常
Base::~Base();
throw;
}
try {
dm3.std::string::string(); //试图构造dm2
}
catch () {
dm1.std::string::~string(); //如果抛出异常,就销毁dm1,dm2和Base class部分,并传播该异常
dm2.std::string::~string();
Base::~Base();
throw;
}
}
相似的分析可对Base的构造函数进行分析,如果Base的构造函数被inlined,所有替换“Base构造函数调用”插入的代码就会被插入到“Derived构造函数调用”内;如果String构造函数也被inlined,Derived构造函数将获得5份“String构造函数”副本。
基于上面的分析,看似空白的Derived构造函数能够inlined并没有那么简单。
4. 总结:
inline函数有很多优点,但也有上述罗列的缺陷和限制;
大部分的编译器面对inline函数都束手无策(不可能在并不存在的函数内设立断点);
因此,inline函数限制在像Person::age那样平淡无奇的函数,或者一定要成为inline的函数(Effective C++ Item46)