1. 程式人生 > >11 More Effective C++—條款14(有效使用異常限定符)

11 More Effective C++—條款14(有效使用異常限定符)

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);

這樣,未知異常就變成了已知異常,方便捕獲和進一步處理。