2009-08-26 151 views
0

展望互聯網的C++腦筋急轉彎,我發現這個例子:爲什麼代碼崩潰?

#include <iostream> 

using namespace std; 

class A { 
public: 
    A() 
    { 
     cout << "A::A()" << endl; 
    } 

    ~A() 
    { 
     cout << "A::~A()" << endl; 
     throw "A::exception"; 
    } 
}; 

class B { 
public: 
    B() 
    { 
     cout << "B::B()" << endl; 
     throw "B::exception"; // <- crashes here 
    } 

    ~B() 
    { 
     cout << "B::~B()"; 
    } 
}; 

int main(int, char**) { 
    try 
    { 
     cout << "Entering try...catch block" << endl; 

     A objectA; 
     B objectB; 

     cout << "Exiting try...catch block" << endl; 
    } 
    catch (const char* ex) 
    { 
     cout << ex << endl; 
    } 

    return 0; 
} 

這是我認爲該計劃會做:

  1. A :: A()時,輸出到屏幕objectA的構造函數被調用。對象A構造成功。
  2. 當調用objectB的構造函數時,B :: B()將被輸出到屏幕。
  3. B的構造函數然後拋出異常。對象B未被成功構建。
  4. objectB的析構函數未被調用,因爲構造函數從未成功完成。
  5. 當try塊退出時,objectA的析構函數將被調用,因爲對象超出了範圍。

但是,當我運行該程序時,它實際上在標記爲<的行上崩潰。任何人都可以解釋那個時候究竟發生了什麼?

+0

如果下面的描述(由所有人)是正確的,那麼它應該仍然在A的析構函數中發生異常時調用terminate()之前先打印A ::〜A()。 – 2009-08-26 15:29:20

回答

11

如果你真的是編碼,不僅僅是令人着迷的永遠不會從析構函數中拋出異常。如果在堆棧展開期間引發異常,則調用terminate()。在你的情況下,A的析構函數在處理B的構造函數中拋出的異常時拋出。

編輯: 更確切地說(如評論中所建議的) - 永遠不要讓異常轉義析構函數。在析構函數內捕獲的異常沒有任何問題。但是如果在堆棧展開程序中必須處理兩個異常 - 導致堆棧展開的那個異常以及在展開期間逃脫析構函數的異常,那麼std::terminate()就會發生。

+0

謝謝。我實際上從這些蝙蝠俠中學到了一些東西。 因此,程序崩潰是因爲在堆棧展開期間拋出異常,還是因爲在處理另一個異常時引發異常? – Andy 2009-08-26 10:10:13

+0

15.2.3:「調用從try塊到throw-expression的路徑上構造的自動對象的析構函數的過程稱爲」堆棧展開「。[注意:如果在堆棧展開期間調用的析構函數退出時出現異常,std :: terminate被調用(15.5.1)。因此析構函數通常應該捕獲異常,而不是讓它們從析構函數中傳播出去。「術語「處理異常」並不精確,通過它可以瞭解堆棧展開過程,何時不允許另一個異常,或異常處理過程(catch塊),其中拋出異常是OK。 – 2009-08-26 10:56:31

+0

更正:如果異常在傳播另一個異常(堆棧展開)時調用析構函數,則調用terminate()。在析構函數中使用異常沒有問題,只要確保在析構函數完成之前捕獲它們即可。 – 2009-08-26 15:22:24

4

在C++中的黃金法則 - 析構函數絕不應該拋出異常。忽略這一規則將導致在各種情況下未定義的行爲。

+0

我認爲它在所有情況下都很好定義(可能是錯誤的)。誠然,它不是一個好主意。 – 2009-08-26 15:23:54

+0

@Martin C++標準指定存儲在容器中的對象不得有析構函數拋出 - 它沒有定義如果他們這樣做會發生什麼。 – 2009-08-26 15:28:52

2

A::~A()在try塊退出時調用,正是因爲該對象超出了範圍。這就是爲什麼RAII工作原理 - 它取決於被調用的析構函數,而不管範圍退出的原因。

A::~A()然後引發異常。由於B::B()的異常仍然被拋出堆棧,這使程序崩潰。

+0

不會崩潰:調用terminate()。 – 2009-08-26 15:30:59

2

你應該從不在析構函數中拋出一個異常。看看this question爲什麼。

6

B::B()引發異常堆棧開始展開。 A::~A()被調用,並拋出另一個例外,A::~A()內沒有捕捉到。

如果另一個異常在堆棧展開正在進行時遺留析構函數,則會調用terminate()並且看起來像程序崩潰。

3

代碼崩潰是因爲B的C'tor拋出一個異常,然後啓動「堆棧展開」過程:所有本地對象被破壞,dus,A的D'tor被調用,並在堆棧展開期間引發異常,然後「中止」被稱爲,因爲不能有兩個例外在同一時間...

結論:

切勿從D'TOR拋出一個異常,因爲你可能棧展開過程中把它扔。

相關問題