簡體   English   中英

析構函數中的c ++異常

[英]c++ exception in destructor

從其他線程,我知道我們不應該在析構函數中拋出異常! 但是對於下面的例子,它確實有效。 這是否意味着我們只能在一個實例的析構函數中拋出異常? 我們該如何理解這個代碼示例!

#include <iostream>
using namespace std;
class A {
 public:
  ~A() {
    try {
      printf("exception in A start\n");
      throw 30;
      printf("exception in A end\n");      
    }catch(int e) {
      printf("catch in A %d\n",e);
    }
  }
};
class B{
 public:
  ~B() {
    printf("exception in B start\n");
    throw 20;
    printf("exception in B end\n");    
  }
};
int main(void) {
  try {
    A a;
    B b;
  }catch(int e) {
    printf("catch in main %d\n",e);
  }
  return 0;
}

輸出是:

exception in B start
exception in A start
catch in A 30
catch in main 20

C ++ 17之前的最佳實踐表示不要讓異常從析構函數中傳播出來 如果析構函數包含一個throw表達式或者調用一個可能拋出的函數,只要拋出異常被捕獲並處理而不是從析構函數中轉義,這樣就可以了。 所以你的A::~A很好。

B::~B的情況下,你的程序在C ++ 03中很好,但在C ++ 11中卻沒有。 規則是,如果你確實讓一個異常從析構函數傳播出來,並且析構函數是一個被堆棧展開直接銷毀的自動對象,那么就會調用std::terminate 由於b未作為堆棧展開的一部分被銷毀,因此將捕獲從B::~B 〜B拋出的異常。 但是在C ++ 11中, B::~B noexcept析構函數將被隱式聲明為noexcept ,因此,允許異常傳播出來將無條件地調用std::terminate

要允許在C ++ 11中捕獲異常,您可以編寫

~B() noexcept(false) {
    // ...
}

仍然存在可能在堆棧展開期間調用B::~B的問題 - 在這種情況下,將調用std::terminate 因為,在C ++ 17之前,無法判斷是否是這種情況,建議絕不允許異常傳播出析構函數。 遵循這條規則,你會沒事的。

在C ++ 17中,可以使用std::uncaught_exceptions()來檢測在堆棧展開期間是否正在銷毀對象。 但你最好知道你在做什么。

“我們不應該在析構函數中拋出異常”的建議並不是絕對的。 問題是當拋出異常時,編譯器開始展開堆棧,直到它找到該異常的處理程序。 展開堆棧意味着調用析構函數來處理因為堆棧框架消失而丟失的對象。 如果其中一個析構函數拋出了一個未在析構函數本身內處理的異常,則會出現此建議的內容。 如果發生這種情況,程序會調用std::terminate() ,有些人認為發生這種情況的風險非常嚴重,以至於他們必須編寫編碼指南來防止它。

在您的代碼中,這不是問題。 B的析構函數拋出異常; 結果,也調用了a的析構函數。 析構函數拋出異常,但處理析構函數內的異常。 所以沒有問題。

如果更改代碼以刪除A的析構函數中的try ... catch塊,則析構函數中拋出的異常不會在析構函數中處理,因此最終會調用std::terminate()

編輯:正如Brian在他的回答中指出的那樣,這個規則在C ++ 11中有所改變:析構函數是隱式noexcept ,所以你的代碼應該在B對象被銷毀時調用terminate 將析構函數標記為noexcept(false) “修復”此問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM