More Effective C++ 11:禁止异常信息传递到析构函数外

栈展开(stack-unwinding)

抛出异常时,将暂停当前函数的执行,开始查找匹配的catch子句。首先检查throw本身是否在try块内部,如果是,检查与该try相关的catch子句,看是否可以处理该异常。如果不能处理,就退出当前函数,并且释放当前函数的内存并销毁局部对象,继续到上层的调用函数中查找,直到找到一个可以处理该异常的catch。这个过程称为栈展开。

有两种情况下会调用析构函数:
1.在正常情况下删除一个对象,例如对象超出了作用域或被显式地delete
2.异常传递的堆栈辗转开解(stack-unwinding)过程
中,由异常处理系统删除一个对象。

在上述两种情况下,调用析构函数时异常可能处于激活状态也可能没有处于激活状态。遗憾的是没有办法在析构函数内部区分出这两种情况。
如果在一个异常被激活的同时,析构函数也抛出异常,并导致程序控制权转移到析构函数外,C++将调用terminate函数,它终止你程序的运行,而且是立即终止,甚至连局部对象都没有被释放。
考虑以下的类:

class Session 
{ 
public: 
	Session(); 
	~Session(); 
	... 
private: 
	static void logCreation(Session *objAddr); 
	static void logDestruction(Session *objAddr); 
};

函数 logCreationlogDestruction 被分别用于记录对象的建立与释放。 我们因此可以这样编写 Session 的析构函数:

Session::~Session() 
{ 
	logDestruction(this); 
}

如果 logDestruction 抛出一个异常,会发生什么事呢?
异常没有被 Session 的析构函数捕获住,所以它被传递到析构函数的调用者那里。但是如果析构函数本身的调用就是源自于某些其它异常的抛出,那么 terminate 函数将被自动调用,彻底终止你的程序。

那么事态果真严重到了必须终止程序运行的地步了么?如果没有,你必须防止在 logDestruction 内抛出的异常传递到 Session 析构函数的外面。唯一的方法是用trycatch blocks。一种很自然的做法会这样编写函数:

Session::~Session() 
{ 
	try 
	{ 
		logDestruction(this); 
	} 
 	catch (...) 
 	{ 
		 cerr << "Unable to log destruction of Session object " 
		 << "at address " 
		 << this 
		 << ".\n"; 
	} 
}

但是这样做并不比你原来的代码安全。如果在 catch 中调用 operator<<时导致一个异常被抛出,我们就又遇到了老问题,一个异常被转递到 Session 析构函数的外面
我们可以在 catch 中放入 try,但是这总得有一个限度,否则会陷入循环。因此我们在释放 Session 时必须忽略掉所有它抛出的异常:

Session::~Session() 
{ 
	 try 
	 { 
	 	logDestruction(this); 
	 } 
	 catch (...) { } 
}

catch 表面上好像没有做任何事情,这是一个假象,实际上它阻止了任何从logDestruction 抛出的异常被传递到 session 析构函数的外面
我们现在能高枕无忧了,无论 session 对象是不是在堆栈退栈(stack unwinding)中被释放,terminate 函数都不会被调用。

不允许异常传递到析构函数外面还有第二个原因:
如果一个异常被析构函数抛出而没有在函数内部捕获住,那么析构函数就不会完全运行。
如果析构函数不完全运行,它就无法完成希望它做的所有事情。

例如,我们对 session 类做一个修改,在建立 session 时启动一个数据库事务(database transaction),终止 session时结束这个事务:

Session::Session() // 为了简单起见,, 
{ // 这个构造函数没有 
 // 处理异常 
	 logCreation(this); 
	 startTransaction(); // 启动 database transaction 
} 
Session::~Session() 
{ 
	 logDestruction(this); 
	 endTransaction(); // 结束 database transaction 
}

如果在这里 logDestruction 抛出一个异常,在 session 构造函数内启动的 transaction就没有被终止。我们也许能够通过重新调整 session 析构函数内的函数调用顺序来消除问题,但是如果 endTransaction 也抛出一个异常,我们除了回到使用 trycatch 外,别无选择。

总结

禁止异常传递到析构函数外,两个原因:
1.能够在异常转递的堆栈辗转开解(stack-unwinding)的过程中,防止 terminate 被调用。
2.帮助确保析构函数总能完成我们希望它做的所有事情

猜你喜欢

转载自blog.csdn.net/qq_44800780/article/details/106312458
今日推荐