11 More Effective C++—条款14/15(有效使用异常限定符/异常处理的成本)

1 异常限定符与unexpected调用

如下面的代码所示,标识符throw()即为异常限定符。异常限定符标识了函数可以抛出的异常类型。当throw后面的括号内容为空,表示该函数不抛出任何异常。

class Exception {
	public:
		const char* what() throw() const; // 该函数不抛出任何异常
		void doSomething() throw(int, Widget); // 该函数只抛出int和Widget类型的异常
}

throw()限定符以文档的形式,规定了函数可以抛出异常类型的范围。但是,多数编译器允许throw()修饰的函数,调用没有throw()修饰的函数。如下面的例子所示。

void func0() {
	// may throw some exception;
}
void func1() throw() {
	func0();
}

在运行时期,若func0()抛出异常,则特殊函数unexpected会被调用,该函数调用terminate函数,terminate默认行为会调用abort。abort停止程序运行,而程序中的局部变量无法被销毁。

2 避免策略

编译器允许带有“异常限定符”的函数调用没有“异常限定符”的函数,这种做法极具弹性,但会带来程序被迫终止,内存泄漏等问题。下面将会讨论如何避免unexpected函数被调用。

1 不要用“异常限定符throw()”修饰带有模板参数的函数

如下面代码。对于判断“==”的操作不会出现异常。但是我们无法确定,取地址操作符“&”是否已经被重载,且可能抛出异常。

此种情况的实质是,我们无法确定,所有类对象的同名函数都不会抛出异常。

template<class T>
bool operator==(const T& left, const T& right) throw() {
	return &left == &right;
}

2 外层函数不使用throw()进行修饰

若被调用的内层函数B没有throw()修饰,则外层的调用函数A也不要有throw()修饰——我们无法确定函数B的的确确不会抛出异常。

我们常常会忽略的一种情况是:注册“回调函数”。如下面代码所示,如果注册的“回调函数”没有throw修饰,而调用“回调函数”的外层函数却有throw修饰,“回调函数”抛出异常就会引起程序终止。

typedef void (*CallbackPtr)();
class Callback {
	public:
		Callback(CallbackPtr func) : m_func(func) {}
		void doSomething() throw() {
			m_func(); // 可能抛出异常
		}
	private:
		CallbackPtr m_func;
}

由于较新的编译器支持typedef后加入throw进行修饰。因此我们可以定义如下函数指针类型。但是,有可能CallbackPtr所指向的函数依然会抛出异常。因此最好还是采用本节最开始的主张——如果不确定内层函数是否会抛出异常,那么外层函数也不要用throw限制。

typedef void (*CallbackPtr)() throw();
void func0();
void func1() throw();
Callback object0(func0); // 错误,func0没有throw修饰
Callback object1(func1);  // 正确,func1有throw修饰

3 自定义unexpected函数

上面提到当throw()修饰的函数抛出异常,则系统会自动调用函数unexpected(),unexpected()函数会调用terminate(),terminate()继续调用abort(),进而终止函数程序。

新的思路是,当出现问题时,抛出自定义函数,而不是调用unexpected()函数。

C++提供了函数set_unexpected(),向该函数中传递我们自定义的函数,来替换默认的unexpected()。如下面的例子所示。自定义函数抛出我们自定义的异常,从而进一步处理。

class UnexpectedException {
}
void unExpected() {
	throw UnexpectedException();
}
set_unexpected(unExpected);

这样,未知异常就变成了已知异常,方便捕获和进一步处理。

3 异常的成本

若在编译过程中加入异常,就会需要额外的数据结构来记录try…catch结构。而代码、运行速度也会整体下降5%-10%。

同时,若异常没有妥善处理,则会造成程序崩溃,退出等情况发生。因此,使用异常的原则是:如果能使用参数传递、返回值,就尽量减少对异常的使用。异常只是出现在很少的一部份内。

猜你喜欢

转载自blog.csdn.net/zhizifengxiang/article/details/83572854