http://blog.csdn.net/hikaliv/archive/2009/05/24/4212864.aspx
文章內容:
http://blog.csdn.net/CARL_SEN/archive/2009/05/04/4148426.aspx
第一部分:
1. 異常發生時,異常對象會沿函數調用棧的反方向拋出,這個過程常稱為棧展開(堆棧解退)。
2. 在棧展開過程中,如果異常對象始終都沒遇到可行的 catch 處理塊,系統將調用 terminate 函數強制終止程式。當然如果連 try 塊都沒有,系統將直接調用 terminate 函數。
3. 在棧展開過程中,編譯器保證適當的撤銷局部對象。每個函數在棧展開退出時,它的局部儲存會釋放,如果局部對象是類類型,則自動調用對象的解構函式。
4. 解構函式應該從不拋出異常,因為解構函式都是自動調用的,不會自動加上 try 測試塊,因此解構函式中異常的拋出將直接導致系統調用 terminate 強制退出。在實踐中,由於解構函式釋放資源,不太可能出現異常,此外標準庫類型都保證它們的解構函式不會引發異常。
5. 如果在建構函式中發生異常,則該對象可能只是部分被構造,即使對象只是部分被構造,也要保證將會適當的撤銷已構造的成員。
6. 不能不處理異常,異常是足夠重要的,使程式不能按正常情況執行的正常事件。不去捕獲異常將直接導致程式的強制終止。
7. catch 子句接收的異常類型可以是內建類型,也可以是類類型,也就是說我們可以拋出(throw)一個如 int 的一般類型作為異常對象。
8. 如果 catch 子句只需瞭解異常的類型,則可以省去形參名,像這樣:catch(runtime_error) {cout<<"runtime error"<<endl;}。當然,如果需要詳細資料必須用形參名來訪問異常對象:catch(runtime_error e) {cout<<e.what()<<endl;}。
9. 使用異常處理,我們能將問題的檢測和問題的解決分離。程式的一部分檢測到的問題可以簡單的將其拋出(throw),檢測部分可以不必瞭解問題如何處理;程式的另一部分(指調用可能拋出異常的模組實現具體功能的部分)則可以通過 try-catch 捕獲異常,然後處理,這樣把異常的處理留給具體功能實現時不失為一個最佳的選擇。
10. 異常處理中,檢測部分拋出對象的類型決定了哪個處理部分的代碼被啟用,被選中的處理代碼是調用鏈中與該物件類型匹配且離拋出異常位置最近的那個。
11. 異常對象與 catch 進行匹配的規則很嚴格,一般除了以下幾種情況外,異常類型必須與 catch 的說明類型完全符合:允許非const到const的轉換,允許衍生類別到基類的轉換,將數組和函數類型轉換為對應的指標。
12. 異常類型匹配將選擇第一個找到的可以處理該異常的 catch,因此 catch 子句列表中,最特殊的 catch 必須最先出現。而帶有因繼承而相關的類型的多個 catch 子句,必須將衍生類別的處理代碼放在基類類型處理代碼之前。
13. throw 運算式拋出的異常對象不同於一般的局部對象,局部對象會在局部模組退出時撤銷,而異常對象由編譯器管理,而且保證駐留在可能被啟用的任意 catch 都可以訪問到的空間中。這個由編譯器管理的異常對象由 throw 運算式建立,並被初始化為被拋出運算式的副本,異常對象將傳給對應的 catch 並在完全處理後才撤銷。
14. 由於異常拋出時都進行了一次副本拷貝,因此異常對象必須是可以複製的。
15. 拋出一個運算式時,被拋出對象的靜態編譯時間類型將決定異常對象的類型。
16. 拋出指標通常是一個壞主意,因為拋出指標要求在對應處理代碼存在的任意地方都存在指標所指向的對象(注意此時 throw 拋出時複製的是指標本身,不會去複製指標指向的內容);而且如果該指標是指向衍生類別對象的基類指標,則那個對象將被分割只拋出基類部分(第 15 條中的靜態類型規則)。
17. 基類異常對象可以用於捕獲衍生類別的異常對象,因此如果 catch 子句處理因繼承而相關的類型,它就應該將自己的形參定義為引用來啟用運行時調用的多態性。
18. catch 可以繼續將捕獲到的異常拋出,它使用不帶運算式的 throw 語句重新將異常拋出,如:throw;。被重新拋出的異常對象是原來的異常對象,與 catch 的形參無關(如原來拋出的是衍生類別 Deriver,catch 形參是基類 Base,則重新拋出後的異常類型是 Deriver),當然如果 catch 形參是引用的話,原來的異常對象可能已被catch修改了。
19. 可以用 catch(...){} 來捕獲所有的異常,catch(...){} 經常與重新拋出運算式結合使用,catch(...) 完成可做的所有局部工作,然後重新拋出異常。
20. 建構函式包括初始化列表的異常處理:
Foo::Foo(int n)<br />try:size(n), array(new int[n])<br />{<br /> //...<br />}<br />catch(const bad_alloc& e)<br />{<br /> //...<br />}
這裡的函數測試塊將初始化列表和函數體中的代碼都納入 try 塊中。
第二部分:
1. 標準異常類定義在四個標頭檔中:exception,new,type_info,stdexcept。
2. exception 中定義了 exception 類,new 中定義了 bad_alloc 類,type_info 中定義了 bad_cast 類,stdexcept 中定義了 runtime_error、logic_error 類。
3. runtime_error 類(表示運行時才能檢測到的異常)包含了 overflow_error、underflow_error、range_error 幾個子類;logic_error 類(一般的邏輯異常)包含了 domain_error、invalid_argument、out_of_range、length_error 幾個子類;而所有的這些類都是 exception 類的子類。
4. exception、bad_alloc、bad_cast 類只定義了預設建構函式,無法在建立這些異常的時候提供附加資訊。其它異常類則只定義了一個接受字串的建構函式,字串初始化式用於為所發生的異常提供更多的資訊。
5. 所有異常類都有一個 what 虛函數,它返回一個指向 C 風格字串的指標。
6. 應用程式可以從 exception 或者中間基類派生自已的異常類來擴充 exception 類層次。
7. 異常說明跟在函數形參表之後,一個異常說明在關鍵字 throw 之後跟著一個由圓括弧括住的異常類型表(由逗號分隔),如:void foo(int) throw(bad_alloc, invalid_argument);。異常列表還可以為空白:void foo(int) throw();,表示該函數不拋出任何異常。
8. 異常說明有用的一種重要情況是,如果函數可以保證不會拋出異常。確定函數將不拋出任何異常,對函數的使用者和對編譯器都是非常有用的。知道函數不拋出異常會簡化編寫該函數異常安全的代碼工作,而編譯器則可以執行被拋出異常抑制的代碼最佳化。
9. 標準異常類中的解構函式和 what 虛函數都承諾不拋出異常,如what的完整聲明為:virtual const char* what() const throw();。
10. 衍生類別中的虛函數不能拋出基類虛函數中沒有聲明的新異常,這樣在編寫代碼時才有一個可依賴的事實:基類中的異常列表是虛函數的衍生類別版本可以拋出的異常列表的超集。
11. 上半部分說過,在異常拋出棧展開的時候,編譯器會適當撤銷函數退出前分配的局部空間,如果局部對象是類類型,則自動調用它的解構函式。但如果在函數內單獨地使用 new 動態分配了記憶體,而且在釋放資源之前發生了異常,那麼棧展開時這個動態空間將不會被釋放。而由類類型對象分配的資源不管是靜態還是動態一般都會適當的被釋放,因為棧展開時保證調用它們的解構函式。因此,在可能存在異常的程式以及分配資源的程式最好使用類來管理那些資源,看一個例子:
void f()<br />{<br /> const int N=10;<br /> int* p=new int[N];<br /> if(...)<br /> {<br /> throw exception;<br /> }<br /> delete[] p;<br />}
當這個異常發生時,p 指向的動態空間將不會被正常撤銷。現在我們用類來管理這個資源:
template <typename T><br />class Resource<br />{<br />private:<br /> unsigned int size;<br /> T* data;<br />public:<br /> Resource(unsigned int _size=0):size(_size),data(new T[_size])<br /> {<br /> for(unsigned int i=0; i<size; ++i) data[i]=T();<br /> }<br /> Resource(const Resource& r):size(r.size),data(new T[r.size])<br /> {<br /> for(unsigned int i=0; i<size; ++i) data[i]=r.data[i];<br /> }<br /> Resource& operator=(const Resource& r)<br /> {<br /> if(&r!=this)<br /> {<br /> size=r.size;<br /> delete[] data;<br /> data=new T[size];<br /> for(int i=0; i<size; ++i) data[i]=r.data[i];<br /> }<br /> return *this;<br /> }<br /> ~Resource() { delete[] data; }<br /> T operator[](unsigned int index);<br /> const T operator[](unsigned int index) const;<br />};<br />void f()<br />{<br /> const int N=10;<br /> Resource<int> p(N);<br /> if(...)<br /> {<br /> throw exception;<br /> }<br />}
這裡即使拋出了異常,也會自動調用對象 p 的解構函式。
12. 異常拋出棧展開的時候,編譯器對局部類對象解構函式的自動運行導致了一個重要的編程技巧的出現,它使程式更為異常安全的。通過定義一個類來封裝資源的分配和釋放,可以保證正確的釋放資源。這一技術常稱為“資源分派即初始化”,簡稱為 RAII。第 11 條已給出了它的用法。
更多參考:
C++ 類建構函式初始化列表的異常機制 function-try block續:為何說 C++ 建構函式初始化列表異常機制是必要的C++ try 塊裡 new 類對像構造異常時發生“回退”並對資源自動釋放