C++異常處理

異常處理是C++的一項語言機制，用於在程式能處理異常事件。異常事件在C++中表示為異常物件。異常事件發生時，由作業系統為程式設定當前異常物件，然後執行程式的當前異常處理程式碼塊，在包含了異常出現點的最內層的codice_1塊，依次匹配同級的codice_2語句。如果匹配codice_2語句成功，則在該catch塊內處理異常；然後執行當前codice_4塊之後的程式碼。如果在當前的codice_4塊沒有能匹配該異常物件的codice_2語句，則由更外一層的codice_4塊處理該異常；如果當前函式內的所有codice_4塊都不能匹配該異常，則遞歸回退到呼叫棧的上一層函式去處理該異常。如果一直回退到主函式codice_9都不能處理該異常，則呼叫系統函式codice_10終止程式。throw.codice_11是一個C++關鍵字，與其後的運算元構成了codice_11語句，語法上類似於codice_13語句。codice_11語句必須被包含在codice_1塊之中；可以是被包含在呼叫棧的外層函式的codice_1中。執行codice_11語句時，其運算元的結果作為物件被複制構造為一個新的物件，放在記憶體的特殊位置。這個新的物件由本級的codice_1所對應的codice_2語句逐個做型別匹配；如果匹配不成功，則與本函式的外層codice_2語句依次做型別匹配；如果在本函式內不能與codice_2語句匹配成功，則遞歸回退到呼叫棧的上一層函式內從函式呼叫點開始繼續與codice_2語句匹配。重複這一過程直到與某個codice_2語句匹配成功或者直到主函式main()都不能處理該異常。因此，codice_11語句丟擲的異常物件不同於一般的區域性物件。一般的區域性物件會在其作用域結束時被析構。而codice_11語句丟擲的異常物件駐留在所有可能被啟用的codice_2語句都能訪問到的記憶體空間中。codice_11語句丟擲的異常物件在匹配成功的codice_2語句的結束處被析構。由於codice_11語句都進行了一次副本複製，因此異常物件應該是可以copy構造的。但對於Microsoft Visual C++編譯器，異常物件的複製建構函式即使私有的情形，異常物件仍然可以被codice_11語句正常丟擲；但在catch語句的引數是傳值時，在catch語句處編譯報錯：“ cannot be caught as the destructor and/or copy constructor are inaccessible”。丟擲一個表示式時，被丟擲物件的靜態編譯時型別將決定異常物件的型別。catch.codice_2語句匹配被丟擲的異常物件時，如果codice_2語句的引數是引用型，則該引數直接引用到throw語句丟擲的異常物件上；如果codice_2引數是傳值的，則複製構造一個新的物件作為codice_2語句的引數的值。在該catch語句結束時，先析構codice_2的傳值的引數物件，然後析構throw語句丟擲的異常物件。codice_2語句匹配異常物件時，不會做任何隱式型別轉換。 異常物件與catch語句進行匹配的規則很嚴格，一般除了以下幾種情況外，異常物件的型別必須與catch語句的宣告型別完全匹配：允許非const到const的轉換；允許派生類到基類的轉換；將陣列和函式型別轉換為對應的指標。在catch塊中可以使用不帶表示式的throw語句將捕獲的異常重新丟擲：throw ;被重新丟擲的異常物件就是當前catch語句捕獲時所匹配的，原本由codice_11語句丟擲的那個異常物件。重新丟擲的異常物件與catch語句的形參無關。如原來丟擲的是派生類Derived，catch語句形參是基類Based，則重新丟擲後的異常型別是Derived。如果catch語句形參是引用型，重新丟擲的原來的異常物件的內容可能已在catch語句內部被修改了。可以用catch(...){ }來捕獲所有的異常。通常在catch(...){ }中，先執行可做的處理，然後重新丟擲異常。catch語句內部產生的新異常，或者“重新丟擲異常”，均不能被同級的codice_4中其他的catch語句捕獲、處理。只能由更外層的catch語句去捕獲該異常。棧展開.棧展開是指當前的codice_4塊匹配成功或者匹配不成功異常物件後，從codice_1塊內異常物件的丟擲位置，到codice_1塊的開始處的所有已經執行了各自建構函式的區域性變數，按照構造生成順序的逆序，依次被析構。如果當前函式內對丟擲的異常物件匹配不成功，則從最外層的codice_1語句到當前函式體的起始位置處的區域性變數也依次被逆序析構，實現棧展開，然後再回退到呼叫棧的上一層函式內從函式呼叫點開始繼續處理該異常。catch語句如果匹配異常物件成功，在完成了對catch語句的引數的初始化之後，對同層級的try塊執行棧展開。由於執行緒執行時，被呼叫的函式的引數、返回地址、區域性變數等都是依函式呼叫次序儲存在函式呼叫棧上。當前被呼叫函式的引數、區域性變數名字可以覆蓋掉早前呼叫函式的同名變數，看起來就是隻有當前函式內的名字可以訪問，早前呼叫的函式內部的名字都不可訪問，就像磁帶被“捲起”。異常處理時按照函式呼叫順序的逆序析構，依次析構各個被調函式的區域性變數，就類似把已經卷起的“磁帶”再展開，抹去上面記錄的資料，故此“棧展開”得名。unwinding在物理學、電工學上也翻譯做“退繞”、“退卷”。C++標準程式庫中定義的異常類.標準異常類定義在C++標準程式庫的四個標頭檔案中：所有的異常類都是exception類的子類。runtime_error類包含了overflow_error、underflow_error、range_error幾個子類；logic_error類包含了domain_error、invalid_argument、out_of_range、length_error幾個子類；各種標準異常類都定義了一個接受字串的建構函式，字串初始化式用於為所發生的異常提供更多的資訊。所有異常類都有一個what()虛擬函式，它返回一個指向C風格字串的指標。應用程式可以從各種標準異常類派生自己的異常類。函式的異常規格.異常規格列出函式可能會丟擲的所有異常的型別。異常規格寫在函式的形參表之後的關鍵字throw之後跟著一對圓括號括住的異常型別列表。如：void foo(int) throw(bad_alloc, invalid_argument)/*函式體*/異常列表還可以為空：void foo(int) throw();表示該函式不丟擲任何異常。如果函式內丟擲的異常的型別不在該函式的異常規格中，則系統函式unexpected()被呼叫。如果在unexpected()中丟擲的異常出現在該函式的異常規格中，則在該函式被呼叫處恢復對異常的catch處理。如果在unexpected()中丟擲的異常不在該函式的異常規格中，則呼叫系統函式terminate()以終止程式。標準異常類中的建構函式、解構函式和what()虛擬函式都承諾不丟擲異常。如what的完整宣告為：virtual const char* what() const throw();派生類中的虛擬函式不能丟擲基類虛擬函式中沒有宣告的新異常。使用函式的異常規格的好處：Microsoft Visual C++接受但暫不支援C++標準中的函式的異常規格。即使使用了編譯器選項/D1ESrt，函式丟擲不在其異常規格中的其他型別異常時，不會自動呼叫unexpected()，而是在該函式呼叫點處的codice_4處理。在Visual C++的函式名字修飾中，函式的形參的型別都編碼入被修飾後的函式名字中；但是函式的異常說明中的型別都沒有編碼入被修飾後的函式名字中。noexcept關鍵字.事實上，異常規格這一特性在程式中很少被使用，因此在C++11中被棄用。C++11定義了新的noexcept關鍵字。如果在函式聲明後直接加上noexcept關鍵字，表示函式不會丟擲異常。另外一種形式是noexcept關鍵字後跟常量表達式，其值轉為布林值，如果為真表示函式不會丟擲異常，反之，則有可能丟擲異常。returnType funcDeclaration (args) noexcept(常量表達式) ;如果保證不丟擲異常的函式卻實際上丟擲異常，則會直接呼叫std::terminate中斷程式的執行。noexcept關鍵字還可以用作運算子，其後的運算元表示式如果有可能丟擲異常，則運算子返回為false；如果運算元表示式保證不丟擲異常，則運算子返回為true。這一運算子用於在定義模板函式時可以根據模板引數型別來確定是否傳出異常。對類解構函式，使用noexcept關鍵字也可以顯式指明不剖出異常。類解構函式預設不丟擲異常。如果宣告為不丟擲異常的類解構函式在執行時丟擲了異常，將導致呼叫std::terminate中斷程式的執行。建構函式、解構函式與異常.建構函式沒有返回值，所以應該用異常來報告發生的問題。建構函式丟擲異常就意味著該建構函式沒有執行完，所以其對應的解構函式不會被自動呼叫，因此建構函式應該先析構所有已初始化的基物件、成員物件，再丟擲異常。解構函式被期望不向函式外丟擲異常。解構函式中向函式外丟擲異常，將直接呼叫terminator()系統函式終止程式。如果一個解構函式內部丟擲了異常，就應該在該解構函式內部捕獲、處理了該異常，不能讓異常被丟擲解構函式之外。建構函式初始化列表的異常機制.C++類建構函式初始化列表的異常機制，稱為function-try block。一般形式為：myClass::myClass(type1 pa1)try: _myClass_val (初始化值)/*建構函式的函式體 */catch ( exception&amp; err )/* 建構函式的異常處理部分 */資源獲取即初始化.資源獲取即初始化是指：為了更為方便、魯棒地釋放已獲取的資源，避免資源死鎖，一個辦法是把資源資料用物件封裝起來。程式發生異常，執行棧展開時，封裝了資源的物件會被自動呼叫其解構函式以釋放資源。例子.using namespace std;class AA{private:public:int s;AA(class AA&amp; rhs)AA(int ihs)try: s ( ihs )catch(...)~AA()void foo() throw ( exception )throw 3.14; //浮點型異常物件不在該函式的異常規格中};int main()AA a(101);tryAA temp(102);temp.foo(); //對於Visual C++，即使用編譯器選項/D1ESrt//仍然會無視AA::foo函式的異常規格throw a;catch (AA&amp; e) //引用型引數，可改為傳值型引數e.s=102; //修改throw丟擲的異常物件cout«"catch AA exception!"«endl;catch(...)cout«"catch all others exception!"«endl;C++異常與Windows作業系統異常處理機制.在Windows作業系統上，編譯器實現的C++異常一般是基於作業系統的異常處理機制。異常處理