C++ 中的例外狀況和堆疊回溯

在 C++ 例外狀況機制中,控制項是從 throw 陳述式移動到可以處理擲回類型的第一個 catch 陳述式。 到達 catch 語句時,擲回和 catch 語句範圍中的所有自動變數都會在稱為 堆疊回溯 的進程中終結。 堆疊回溯中的執行方式如下:

  1. 控制會透過一般循序執行到達 try 語句。 會執行 區塊中的 try 受防護區段。

  2. 如果在受防護區段執行期間未擲回任何例外狀況, catch 則不會執行區塊後面的 try 子句。 執行會在最後一個遵循相關聯 try 區塊的 catch 子句之後,繼續在 語句上執行。

  3. 如果在執行受防護區段期間或直接或間接呼叫受防護區段的任何常式中擲回例外狀況,則會從運算元所 throw 建立的物件建立例外狀況物件。 (這表示可能涉及複製建構函式。此時,編譯器會在較高的執行內容中尋找 catch 子句,該子句可以處理擲回之類型的例外狀況,或 catch 處理任何類型的例外狀況的處理常式。 catch處理常式會依區塊之後 try 的外觀順序進行檢查。 如果找不到適當的處理常式,則會檢查下一個動態封入區塊 try 。 此程式會繼續進行,直到檢查最外層封入區塊 try 為止。

  4. 如果仍然找不到相符的處理常式,或是回溯過程中、處理常式取得控制之前發生例外狀況,則會呼叫預先定義的執行階段函式 terminate。 如果在擲回例外狀況後但回溯開始之前發生例外狀況,則會呼叫 terminate

  5. 如果找到相符 catch 的處理常式,而且會依值攔截,則會藉由複製例外狀況物件來初始化其正式參數。 如果它是以傳址方式攔截,則會初始化參數以參考例外狀況物件。 在型式參數初始化之後,回溯堆疊的處理序才會開始。 這包括在與 catch 處理常式相關聯之區塊開頭 try 與例外狀況擲回月臺之間,完整建構但尚未解構的所有自動物件。 解構會依照建構的反向順序進行。 處理常式 catch 會執行,而且程式會在最後一個處理常式之後繼續執行,也就是第一個語句或不是處理常式的 catch 建構。 控制項只能透過擲回的例外狀況來輸入 catch 處理常式,絕不會透過 goto 語句或 case 語句中的 switch 標籤。

堆疊回溯範例

下列範例將示範堆疊如何在擲回例外狀況時回溯。 在執行緒上的執行會從 C 中的 throw 陳述式跳至 main 中的 catch 陳述式,然後一路回溯每個函式。 請注意 Dummy 物件建立的順序,以及物件超出範圍後終結的順序。 另請注意,除了包含 catch 陳述式的 main 之外,其他函式都不會完成。 函式 A 絕不會從其對 B() 的呼叫傳回,而且 B 絕不會從其對 C() 的呼叫傳回。 如果您取消 Dummy 指標定義和對應 delete 陳述式的註解,然後執行程式,請注意指標絕不會刪除。 這就說明函式未提供例外狀況保證時,可能發生的狀況。 如需詳細資訊,請參閱 如何:設計例外狀況 。 如果您註解 catch 陳述式,就可以觀察程式因為未處理的例外狀況結束時,會發生什麼情況。

#include <string>
#include <iostream>
using namespace std;

class MyException{};
class Dummy
{
    public:
    Dummy(string s) : MyName(s) { PrintMsg("Created Dummy:"); }
    Dummy(const Dummy& other) : MyName(other.MyName){ PrintMsg("Copy created Dummy:"); }
    ~Dummy(){ PrintMsg("Destroyed Dummy:"); }
    void PrintMsg(string s) { cout << s  << MyName <<  endl; }
    string MyName;
    int level;
};

void C(Dummy d, int i)
{
    cout << "Entering FunctionC" << endl;
    d.MyName = " C";
    throw MyException();

    cout << "Exiting FunctionC" << endl;
}

void B(Dummy d, int i)
{
    cout << "Entering FunctionB" << endl;
    d.MyName = "B";
    C(d, i + 1);
    cout << "Exiting FunctionB" << endl;
}

void A(Dummy d, int i)
{
    cout << "Entering FunctionA" << endl;
    d.MyName = " A" ;
  //  Dummy* pd = new Dummy("new Dummy"); //Not exception safe!!!
    B(d, i + 1);
 //   delete pd;
    cout << "Exiting FunctionA" << endl;
}

int main()
{
    cout << "Entering main" << endl;
    try
    {
        Dummy d(" M");
        A(d,1);
    }
    catch (MyException& e)
    {
        cout << "Caught an exception of type: " << typeid(e).name() << endl;
    }

    cout << "Exiting main." << endl;
    char c;
    cin >> c;
}

/* Output:
    Entering main
    Created Dummy: M
    Copy created Dummy: M
    Entering FunctionA
    Copy created Dummy: A
    Entering FunctionB
    Copy created Dummy: B
    Entering FunctionC
    Destroyed Dummy: C
    Destroyed Dummy: B
    Destroyed Dummy: A
    Destroyed Dummy: M
    Caught an exception of type: class MyException
    Exiting main.

*/