C++ 異常處理

異常處理（exception handling）是 C++ 的一項語言機制，用於在程式能處理異常事件。

異常事件在 C++ 中表示為異常物件（exception object）。異常事件發生時，由作業系統為程式設定當前異常物件，然後執行程式的當前異常處理程式碼塊，在包含了異常出現點的最內層的 try 塊，依次匹配同級的 catch 語句。如果匹配 catch 語句成功，則在該 catch 塊內處理異常；然後執行當前 try...catch... 塊之後的程式碼。如果在當前的 try...catch... 塊沒有能匹配該異常物件的 catch 語句，則由更外一層的 try...catch... 塊處理該異常；如果當前函式內的所有 try...catch... 塊都不能匹配該異常，則遞歸回退到呼叫棧的上一層函式去處理該異常。如果一直回退到主函式 main 都不能處理該異常，則呼叫系統函式 terminate 終止程式。

throw

throw 是一個 C++ 關鍵字，與其後的運算元構成了 throw 語句，語法上類似於 return 語句。throw 語句必須被包含在 try 塊之中；可以是被包含在呼叫棧的外層函式的 try 中。

執行 throw 語句時，其運算元的結果作為物件被複制構造為一個新的物件，放在記憶體的特殊位置（既不是堆也不是棧，Windows 上是放在 “執行緒資訊塊 TIB” 中）。這個新的物件由本級的 try 所對應的 catch 語句逐個做型別匹配；如果匹配不成功，則與本函式的外層 catch 語句依次做型別匹配；如果在本函式內不能與 catch 語句匹配成功，則遞歸回退到呼叫棧的上一層函式內從函式呼叫點開始繼續與 catch 語句匹配。重複這一過程直到與某個 catch 語句匹配成功或者直到主函式 main 都不能處理該異常。

因此，throw 語句丟擲的異常物件不同於一般的區域性物件。一般的區域性物件會在其作用域結束時被析構。而 throw 語句丟擲的異常物件駐留在所有可能被啟用的 catch 語句都能訪問到的記憶體空間中。

throw 語句丟擲的異常物件在匹配成功的 catch 語句的結束處被析構（即使該 catch 語句使用的是非 “引用” 的傳值引數型別）。

由於 throw 語句都進行了一次副本複製，因此異常物件應該是可以 copy 構造的。但對於 Microsoft Visual C++ 編譯器，異常物件的複製建構函式即使私有的情形，異常物件仍然可以被 throw 語句正常丟擲；但在 catch 語句的引數是傳值時，在 catch 語句處編譯報錯：“cannot be caught as the destructor and/or copy constructor are inaccessible”。

丟擲一個表示式時，被丟擲物件的靜態編譯時型別將決定異常物件的型別。

catch

catch 語句匹配被丟擲的異常物件時，如果 catch 語句的引數是引用型，則該引數直接引用到 throw 語句丟擲的異常物件上；如果 catch 引數是傳值的，則複製構造一個新的物件作為 catch 語句的引數的值。在該 catch 語句結束時，先析構 catch 的傳值的引數物件，然後析構 throw 語句丟擲的異常物件。

catch 語句匹配異常物件時，不會做任何隱式型別轉換（implicit type conversion），包括型別提升（promotion）。 異常物件與 catch 語句進行匹配的規則很嚴格，一般除了以下幾種情況外，異常物件的型別必須與 catch 語句的宣告型別完全匹配：允許非 const 到 const 的轉換；允許派生類到基類的轉換；將陣列和函式型別轉換為對應的指標。

在 catch 塊中可以使用不帶表示式的 throw 語句將捕獲的異常重新丟擲：

throw ;

被重新丟擲的異常物件就是當前 catch 語句捕獲時所匹配的，原本由 throw 語句丟擲的那個異常物件。重新丟擲的異常物件與 catch 語句的形參無關。如原來丟擲的是派生類 Derived，catch 語句形參是基類 Based，則重新丟擲後的異常型別是 Derived。如果 catch 語句形參是引用型，重新丟擲的原來的異常物件的內容可能已在 catch 語句內部被修改了。

可以用 catch (...) 來捕獲所有的異常。通常在 catch (...){ } 中，先執行可做的處理，然後重新丟擲異常。

catch 語句內部產生的新異常，或者 “重新丟擲異常”，均不能被同級的 try...catch... 中其他的 catch 語句捕獲、處理。只能由更外層的 catch 語句去捕獲該異常。

棧展開

棧展開（unwinding）是指當前的 try...catch... 塊匹配成功或者匹配不成功異常物件後，從 try 塊內異常物件的丟擲位置，到 try 塊的開始處的所有已經執行了各自建構函式的區域性變數，按照構造生成順序的逆序，依次被析構。如果當前函式內對丟擲的異常物件匹配不成功，則從最外層的 try 語句到當前函式體的起始位置處的區域性變數也依次被逆序析構，實現棧展開，然後再回退到呼叫棧的上一層函式內從函式呼叫點開始繼續處理該異常。

catch 語句如果匹配異常物件成功，在完成了對 catch 語句的引數的初始化（對傳值引數完成了引數物件的 copy 構造）之後，對同層級的 try 塊執行棧展開。

由於執行緒執行時，被呼叫的函式的引數、返回地址、區域性變數等都是依函式呼叫次序儲存在函式呼叫棧（即執行緒執行時棧）上。當前被呼叫函式的引數、區域性變數名字可以覆蓋掉早前呼叫函式的同名變數，看起來就是隻有當前函式內的名字可以訪問，早前呼叫的函式內部的名字都不可訪問，就像磁帶被 “捲起”。異常處理時按照函式呼叫順序的逆序析構，依次析構各個被調函式的區域性變數，就類似把已經卷起的 “磁帶” 再展開，抹去上面記錄的資料，故此 “棧展開” 得名。unwinding 在物理學、電工學上也翻譯做 “退繞”、“退卷”。

C++ 標準程式庫中定義的異常類

標準異常類定義在 C++ 標準程式庫的四個標頭檔案中：

中定義了 exception 類
中定義了 bad_alloc 類
中定義了 bad_cast 類
中定義了 runtimeerror、logicerror 類

所有的異常類都是 exception 類的子類。

runtimeerror 類（表示執行時才能檢測到的異常）包含了 overflowerror、underflowerror、rangeerror 幾個子類；

logicerror 類（一般的邏輯異常）包含了 domainerror、invalidargument、outofrange、lengtherror 幾個子類；

各種標準異常類都定義了一個接受字串的建構函式，字串初始化式用於為所發生的異常提供更多的資訊。所有異常類都有一個 what 虛擬函式，它返回一個指向 C 風格字串的指標。

應用程式可以從各種標準異常類派生自己的異常類。

函式的異常規格

異常規格（exception specification）列出函式可能會丟擲的所有異常的型別。異常規格寫在函式的形參表之後的關鍵字 throw 之後跟著一對圓括號括住的異常型別列表。如：

void foo (int) throw (badalloc, invalidargument)

{/ 函式體 /}

異常列表還可以為空：

void foo (int) throw ;

表示該函式不丟擲任何異常。

如果函式內丟擲的異常的型別不在該函式的異常規格中，則系統函式 unexpected 被呼叫。如果在 unexpected 中丟擲的異常出現在該函式的異常規格中，則在該函式被呼叫處恢復對異常的 catch 處理。如果在 unexpected 中丟擲的異常不在該函式的異常規格中，則呼叫系統函式 terminate 以終止程式。

標準異常類中的建構函式、解構函式和 what 虛擬函式都承諾不丟擲異常。如 what 的完整宣告為：virtual const char* what const throw ;

派生類中的虛擬函式不能丟擲基類虛擬函式中沒有宣告的新異常。

使用函式的異常規格的好處：

丟擲一個物件而不是用函式返回值判斷是否出錯；
函式呼叫序列中間的函式可以不考慮異常處理，由某一層函式呼叫撲捉異常；
unwinding 自動發生。

Microsoft Visual C++ 接受但暫不支援 C++ 標準中的函式的異常規格。即使使用了編譯器選項 / D1ESrt，函式丟擲不在其異常規格中的其他型別異常時，不會自動呼叫 unexpected ，而是在該函式呼叫點處的 try...catch... 處理。在 Visual C++ 的函式名字修飾（name mangling）中，函式的形參的型別都編碼入被修飾後的函式名字中；但是函式的異常說明中的型別都沒有編碼入被修飾後的函式名字中。

noexcept 關鍵字

事實上，異常規格這一特性在程式中很少被使用，因此在 C++11 中被棄用。C++11 定義了新的 noexcept 關鍵字。如果在函式聲明後直接加上 noexcept 關鍵字，表示函式不會丟擲異常。另外一種形式是 noexcept 關鍵字後跟常量表達式，其值轉為布林值，如果為真表示函式不會丟擲異常，反之，則有可能丟擲異常。

returnType funcDeclaration (args) noexcept (常量表達式) ;

如果保證不丟擲異常的函式卻實際上丟擲異常，則會直接呼叫 std::terminate 中斷程式的執行。

noexcept 關鍵字還可以用作運算子，其後的運算元表示式如果有可能丟擲異常，則運算子返回為 false；如果運算元表示式保證不丟擲異常，則運算子返回為 true。這一運算子用於在定義模板函式時可以根據模板引數型別來確定是否傳出異常。

對類解構函式，使用 noexcept 關鍵字也可以顯式指明不剖出異常。類解構函式預設不丟擲異常。如果宣告為（或預設）不丟擲異常的類解構函式在執行時丟擲了異常，將導致呼叫 std::terminate 中斷程式的執行。

建構函式、解構函式與異常

建構函式沒有返回值，所以應該用異常來報告發生的問題。建構函式丟擲異常就意味著該建構函式沒有執行完，所以其對應的解構函式不會被自動呼叫，因此建構函式應該先析構所有已初始化的基物件、成員物件，再丟擲異常。

解構函式被期望不向函式外丟擲異常。解構函式中向函式外丟擲異常，將直接呼叫 terminator 系統函式終止程式。如果一個解構函式內部丟擲了異常，就應該在該解構函式內部捕獲、處理了該異常，不能讓異常被丟擲解構函式之外。

7.1 建構函式初始化列表的異常機制

C++ 類建構函式初始化列表的異常機制，稱為 function-try block。一般形式為：

myClass::myClass (type1 pa1)

try: myClassval (初始化值)

{/ 建構函式的函式體 /}

catch (exception& err)

{/ 建構函式的異常處理部分 /};

例子

include

using namespace std;

class AA {

private:	

public:

	int s;

	AA (class AA& rhs)

{ cout

C++ 異常與 Windows 作業系統異常處理機制

在 Windows 作業系統上，編譯器實現的 C++ 異常一般是基於作業系統的異常處理機制。