Effective C++ Item30:inline函数

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的候选人,但这个构造函数中实际执行的内容并非我们看到的“空白”。

  1. 当使用new,动态创建的对象被其构造函数自动初始化;使用delete,对应的析构函数被调用。
  2. 当创建一个对象,该对象的每一个base class及每一个成员变量都会被自动构造;当销毁一个对象,反向程序的析构行为也自动发生。
  3. 如果有个异常在对象的构造期间被抛出,该对象已经构造好的那一部分就会被自动销毁。

基于上述三点,可以分析在看似“空白”的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)



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值