【Effective C++】不要讓異常逃離析構函數

首先先拋出結論:

  • 析構函數絕對不要拋出異常。如果在一個析構函數中某個函數可能調用失敗,拋出異常,這個析構函數應該捕捉這個異常,然後“吞下”他們,或者結束程序。
  • 如果客戶端需要對某個操作函數運行期間拋出的異常做出反應,那麼這個 class 應該提供一個普通函數接口,而不是在析構函數中自己處理異常。


    假如有以下的代碼片段:
class Widget {
public:
	...
	~Widget() { }	//假設拋出異常
};

void DoSomething()
{
	std::vector<Widget> v;
	...
}

當 vector 容器被銷燬的時候,它是有責任銷燬它內含的所有的 Widget 對象的。但是假如,v 在析構第一個 Widget 對象的時候,Widget 對象的析構函數拋出異常,但是其他的 Widget 對象仍然需要正常被銷燬。如果多個 Widget 對象都拋出異常,這個時候就會造成程序不是結束執行就是會導致不明確行爲。

那如果在析構函數中必須要執行某個動作,但是這個動作又可能會在失敗的時候拋出異常呢?比如說下面這一種情況:
有一個 DBConnection 類負責與數據庫的連接:

class DBConnection {
public:
	static DBConnection create();
	void close();
	//...
};

然後又一個 DBConnectionManager 類負責管理 DBConnection 對象的連接狀態,爲了保證在用戶在忘記調用 DBConnection 對象的 close() 函數的時候,數據庫的連接也能夠正確的關閉,一個合理的想法就是在 DBConnectionManager 類的析構函數中調用 DBConnection 的 close() 函數,大概是這個樣子:

class DBConnectionManager {
public:
	DBConnectionManager(DBConnection db)
	{
		dbConnection = db;
	}

	~DBConnectionManager()
	{
		dbConnection.close();
	}

private:
	DBConnection dbConnection;
};

這個時候就會存在一個問題:如果 DBConnection 的 close 函數拋出異常,DBConnectionManager 的析構函數就會傳播這個異常,也就是會允許程序離開這個析構函數,這個時候就會造成潛在的問題。


有兩個辦法可以避免這個問題:
第一個就是讓這個析構函數把異常給“吞掉”,但是有一個前提條件就是在這個析構函數把異常“吞掉”之後也要保證程序能夠繼續可靠的執行。

~DBConnectionManager()
{
	try {
		dbConnection.close();
	}
	catch (std::exception)
	{
		//記錄調用失敗
	}
}

第二個解決辦法就是在拋出異常的時候就強行結束程序,阻止異常從析構函數中傳播出去,產生不明確行爲
~DBConnectionManager()
{
	try {
		dbConnection.close();
	}
	catch (std::exception)
	{
		//記錄調用失敗
		std::abort();
	}
}

前兩者的解決方案都會導致客戶端無法對“導致 close 拋出異常” 的情況做出反應,所以一個更好的解決方案是重新設計 DBConnectionManager 的接口,使得客戶端能夠對 close 拋出的異常做出反應,也就是提供客戶端手動調用 close 函數的接口,但是爲防止客戶端忘記手動關閉,又會在其析構函數中調用 close 函數。這樣的話,即使真的有錯誤發生,也是客戶端“活該”。(都給你接口了讓你手動調用關閉,誰讓你忘記的,就不要怪我自己處理的時候出錯咯)
class DBConnectionManager {
public:
	DBConnectionManager(DBConnection db)
	{
		dbConnection = db;
	}

	void closeConnection()
	{
		dbConnection.close();
		hasClosed = true;
	}

	~DBConnectionManager()
	{
		if (!hasClosed)
		{
			try {

				dbConnection.close();
			}
			catch (std::exception)
			{
				//記錄調用失敗
			}
		}
	}

private:
	DBConnection dbConnection;
	bool hasClosed;
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章