C++學習大綱:異常處理機制總結


C++學習大綱:異常處理機制總結

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><typeinfo>頭文件)/<typeinfo>/<stdexcep>/<exception>

其中,類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異常。

————————————————


C++學習大綱:異常處理機制總結

通過分享實用的計算機編程語言乾貨,推動中國編程到2025年基本實現普及化,使編程變得全民皆知,最終實現中國編程之崛起,這裡是中國編程2025,感謝大家的支持。

原文鏈接:https://blog.csdn.net/MulinB/article/details/1763240


分享到:


相關文章: