C++異常處理機制總結
參考文檔:《C++編程思想》《C++Primer》《More effective C++》
一、 傳統的錯誤處理機制:
1. 返回值或全局錯誤狀態標誌。
缺點:需要冗長的錯誤檢查代碼。
2. C standard Library中的信號處理系統,signal函數。
缺點:信號處理機制比較複雜;耦合度高;複雜系統中的信號容易產生衝突。
3. C standard Library中的非局部跳轉函數,setjmp和longjmp。
缺點: C++的類析構函數不會被調用,對象不能正確被清理,造成內存洩漏;耦合度高。
二、 C++異常處理機制介紹:
1. 語法:
try
{
throw 3.0;
}
catch (int) //如果處理塊內不需要該變量,可以省略形參
{
cout << "int exception happened!" << endl;
}
catch (double d)
{
cout << d << " double exception happened!" << endl;
}
catch (…)
{
cout << "unknown exception happened!" << endl;
}
2. 說明:
1) throw 可以拋出一個任意類型的變量(或表達式)(包括內置類型如int的變量,或者自定義的類型的變量),catch 按照被拋出變量的編譯時類型進行匹配,找到第一個匹配的類型即進入異常處理。
2) 異常處理是為了保證使程序能夠不異常退出。
3) 建議:儘量不要使用throw拋出內置類型的變量。
4) 如果throw拋出的異常找不到匹配的類型,最終程序將調用C standard Library的terminate函數,程序將異常退出。
5) 當程序跳出try塊時,try塊內的局部變量被自動釋放,對象的析構函數被調用。所以,為了保證程序不異常退出,應該保證析構函數不會拋出異常。
6) 調用函數時,程序的控制權最終還會返回到函數的調用處,但是當你拋出一個異常時,控制權永遠不會回到拋出異常的地方。
7) 異常匹配時,只允許三種類型轉換:const與非const;派生類與基類;數組與指針。(注意:不允許算術轉換.)
8) 建議:catch子句的次序必須反映類型層次,派生類放到基類前面。
9) throw出的對象稱為異常對象(exception object),由編譯器管理,catch接受到的對象如果不是引用或指針的話,則進行對象拷貝。但是異常對象是程序結束才被釋放。
10) 異常可以發生在構造函數中或者構造函數初始化式中。注意:如果異常發生在構造函數中,對象的析構函數將不會被調用!所以需要在構造函數中進行try-catch自己釋放資源。另外,為了處理構造函數初始化式中可能發生的異常,語法應該修改為如下:
//normal constructor
UserClass(): m_nA(1), m_nB(2) { /*constructor body*/ }
//constructor using function try block
UserClass() try: m_nA(1), m_nB(2){ /*constructor body*/ }catch(…){ }
11) 標準庫異常類定義在<exception><stdexcep>頭文件中。/<stdexcep>/<exception>
3. 重新拋出:
在catch塊或被catch塊調用的函數中,可以用”throw;”語句(throw空對象)將異常重新拋出。
4. 異常規格說明(exception specification):
說明函數將會拋出什麼類型的異常。
例子:
void func(int i) throw (runtime_error); //說明該函數func有可能拋出runtime_error異常
void func(int i) throw(); //說明函數func不會拋出任何異常
1) 如果函數運行時拋出了其他類型的異常,程序將會調用標準庫的unexpected函數,該函數將調用terminate退出程序。
2) 派生類的虛函數的異常規格說明只能和基類一樣或比基類更嚴格,不能增加新的異常類型。
3) 注意:一般只會使用throw()來說明一個函數是安全的,不會拋出任何異常,這樣編譯器就可以對調用該函數的代碼做出優化。一般不會使用其他的異常規格說明。
5. 標準異常庫:(見<exception><stdexcep>
其中,類exception的定義如下:
class exception
{
public:
exception();
exception(const char* const &);
exception(const exception&);
exception& operator= (const exception&);
virtual ~exception();
virtual char* what() const; //獲得信息
private:
const char* _m_what;
int _m_doFree;
};
三、 使用異常處理機制的建議:
來自《C++編程思想》和《More Effective C++》的建議:
1. 不要使用異常的情形:
1) 絕對不要在異步事件中使用異常。如使用了信號機制的系統、中斷處理程序等。
2) 不要在處理簡單錯誤的時候使用異常。一般情況下,自己可以處理的錯誤就直接處理,只有當在此處處理不了的錯誤才拋出到更大的語境中。
3) 不要將異常用於流程控制,比如代替switch語句。因為異常處理效率非常低,而且編譯器會做出很多程序員不知道的事情。
4) 遇到不可恢復的錯誤時,最好不用處理異常,直接將異常交給操作系統處理即可。
2. 應該使用異常的情形:
1) 遇到可以修正的錯誤,通過一些行為可以使程序繼續執行;
2) 在當前的語境中不能完全處理的錯誤,但有可能在較高層的語境中可以處理,這時,可以通過將一個同樣類型或者不同類型的異常拋出;
3) 發生不足以讓程序退出的錯誤,但是你認為該錯誤是致命錯誤的時候,可以通過拋出異常便於及時發現故障而終止程序;
4) 為了簡化錯誤處理。
3. 對使用異常的建議(1,4,5,6見MoreEffectiveC++):
1) 慎重使用異常規格說明:當不確定函數拋出何種異常時,最好不要使用異常規格說明。
2) 儘量使用標準異常庫的異常類:編寫自定義的異常類之前,先查看標準異常庫。如果標準異常庫沒有需要的語義的異常,儘量從標準異常類派生出自定義的異常類。
3) 建立自己的異常類層次結構。
4) 儘量通過引用來捕捉異常,原因有二:防止對象拷貝;防止對象slicing。異常對象由編譯器統一管理,可以放心使用引用來操作。
5) 可以在構造函數中拋出異常:如果構造函數發生故障而沒有及時發現,程序繼續運行會造成不可預料的災難性結果,這種情況下可以在構造函數中拋出異常。
6) 不要在析構函數中拋出異常。所以有時候需要在析構函數中處理異常。
四、 編碼規範:
1. 函數的參數檢查不合法時,拋出標準庫的invalid_argument(logic_error子類)異常。
2. 調用函數時,應該檢查logic_error異常,並做相應處理,該異常不足以使程序終止。
3. 發生致命邏輯錯誤或者不可恢復錯誤時,不要捕捉拋出的異常,直接將其交給操作系統處理,退出程序。
4. 儘量使用標準異常庫的異常類,沒有合適的標準異常時,建立自己的異常層次結構。
5. 自定義異常類時,應該從標準異常庫中的類派生,異常類的名稱應該能反映出錯誤的類型。
6. 使用引用來捕捉異常。
7. 不要使用catch(…)。
8. 捕捉到未知異常時,要重新拋出,以免發生故障後繼續運行程序,造成不可預料的後果。
9. 儘量將資源釋放寫入析構函數,這樣防止發生異常時,資源不能釋放。
10. 不要使用異常規格說明。??討論??
11. 建立一個系統的異常基類,將所有系統中發生的異常都封裝成該基類的子類。 ??討論??
五、 例子:
#include <iostream>
#include <exception>
using namespace std;
void func(int i)
{
if (i>100)
{
throw invalid_argument("invalid argument: argument can not be bigger than 100");
}
//do something…
}
int main()
{
int i;
cout << "Please input a number less than 100:" << endl;
cin >> i;
try
{
func(i);
}
catch (logic_error& e) //caught by reference
{
cout << "invalid input: " << e.what() << endl; //logic_error, can be handled, do not need to abort
}
catch (exception&)
{
//do something…
throw; //unknown exception caught, throw again
}
return 0;
}
六、 對使用new操作符分配內存失敗的說明:
較新的C++標準中規定,普通的new操作符失敗時,拋出std::bad_alloc異常。但是在以前,new失敗是簡單的返回一個NULL指針。在一定的環境下,返回一個NULL指針來表示一個失敗依然是一個不錯的選擇。C++標準委員會意識到這個問題,所以他們決定定義一個特別的new操作符版本,這個版本返回0表示失敗。這就是nothrow new。
nothrow new只是簡單的給operator new加了一個參數。
相關代碼如下:
class nothrow_t // in namespace std
{}; //empty class
extern const nothrow_t nothrow; //an object , in namespace std
//declarations from
void * operator new (size_t size, const std::nothrow_t &);
//array version
void * operator new[] (size_t size, const std::nothrow_t &);
所以,使用nothrow new時,可以如下使用:
#include
#include <iostream> // for std::cerr/<iostream>
#include <cstdlib> // for std::exit()/<cstdlib>
Task * ptask = new (std::nothrow) Task;
if (!ptask)
{
std::cerr<
std::exit(1);
}
也可以創建你自己的nothrow_t對象來完成相同的效應:
#include
std::nothrow_t nt;
Task * ptask = new (nt) Task; //user-defined argument
if (!ptask)
//...
值得一提的是,在VC6和VC2005編譯器下,默認的new操作符失敗的行為也是簡單的返回一個NULL指針,它們並不是遵循C++標準拋出一個std::bad_alloc異常。
————————————————
通過分享實用的計算機編程語言乾貨,推動中國編程到2025年基本實現普及化,使編程變得全民皆知,最終實現中國編程之崛起,這裡是中國編程2025,感謝大家的支持。
原文鏈接:https://blog.csdn.net/MulinB/article/details/1763240
閱讀更多 中國編程2025 的文章