条款30:透彻了解inlining的里里外外

  • inlie函数背后整体观念是,将"对此函数的每一个调用"都以函数本体替换之
  • Inlining在大多数C++程序中是编译行为
  • 大部分编译器拒绝将太过复杂(例如循环或递归)的函数inlining,而所有对virtual函数的调用也会使inlining落空,因为virtual意味着"等待,知道运行期才确定调用那个函数",而inling意味着"执行前,先将调用动作替换为被调用函数的本体";
  • 编译器通常不对"通过函数指针而进行的调用"实施inlining,这意味着对inline函数的调用有可能被inlined,也有可能不被inlined,如下代码:
inine void f() {}	// 假设编译器有意愿inline"对f的调用"
void (* pf) () = f; // pf指向f
//...
f();				// 这个调用将被inlined,因为是一个正常调用
pf();				// 这个调用或许不被inlined,因为它通过函数指针达成
  • 构造函数和析构函数往往是inlining的糟糕候选人,考虑如下代码
class Base {
public:
	// ...
private:
	std::string bm1, bm2;
};

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

Derived构造函数看起来是inlining的绝佳候选人,因为他根本不含任何代码,但是事实可以理解为如下:

Derived::Derived() { // "空白Derived构造函数"的观念性实现
	Base::Base();				  // 初始化Base成分
	try {
		dm1.std::string::string();// 试图构造dm1		
	} catch (...) {				
		Base::~Base();			  // 如果抛出异常就销毁base class成分,并传播该异常
		throw;
	}
	try {
		dm2.std::string::string();// 试图构造dm2		
	} catch (...) {				
		dm1.std::string::~string();// 如果抛出异常就销毁dm1,销毁base class成分,并传播该异常
		Base::~Base();			  
		throw;
	}
	try {
		dm3.std::string::string();// 试图构造dm3
	} catch (...) {
		dm2.std::string::~string(); // 如果抛出异常就销毁dm2,销毁dm1,销毁base class成分,并传播该异常
		dm1.std::string::~string();
		Base::~Base();			  
		throw;
	}		
}

以上代码并不能代表编译器真正制造出来的代码,因为现实情况是编译器会以更精致的做法处理异常;Derived构造函数至少一定会陆续调用其成员变量和base class两者的构造函数,如果Base构造函数被inlined,所有替换"Base构造函数调用"而插入的代码也会被插入到"Derived构造函数调用"内;如果string构造函数恰巧也别inlined,则Derived构造函数将获得五份"string 构造函数代码"副本,每一份副本对应于Derived对象的五个字符串(两个来自继承,三个来自自己的声明)之一;

  • 还有一个事实就是大部分调试器对inline函数都束手无策,因为你无法在一个并不存在的函数内打断点调试
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值