C++异常处理

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在物理學、電工學上也翻譯做「退繞」、「退卷」。

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& err )  
{ 
  /* 构造函数的异常处理部分 */};

資源獲取即初始化

資源獲取即初始化（Resource acquisition is initialization，RAII）是指：為了更為方便、魯棒地釋放已獲取的資源，避免資源死鎖，一個辦法是把資源數據用對象封裝起來。程序發生異常，執行棧展開時，封裝了資源的對象會被自動調用其析構函數以釋放資源。

例子

#include <iostream>#include <exception>using namespace std;class AA{private:

public:
int s;
AA(class AA& rhs)
            {  cout<<"copy ctor;"<<endl; }
        AA(int ihs) 
                try:  s ( ihs )
           { cout<<"defautl ctor;"<<endl; }
                catch(...)
                    { }
        ~AA()
               { cout<<"dtor:"<<s<<endl; }
void foo()  throw ( exception )
{
           throw 3.14; //浮点型异常对象不在该函数的异常规格中
        }};
 
int main(){   
       AA a(101);

        try
{
                AA temp(102);
                temp.foo(); //对于Visual C++，即使用编译器选项/D1ESrt
                                   //仍然会无视AA::foo函数的异常规格
throw a; 
}
catch (AA& e) //引用型参数，可改为传值型参数
{
e.s=102;  //修改throw抛出的异常对象
cout<<"catch AA exception!"<<endl;
} 
catch(...)
{
cout<<"catch all others exception!"<<endl;
}}

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

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