2016-04-02 53 views
6

讓說我有RAII類:C++使用與析構函數RAII拋出

class Raii { 
    Raii() {}; 
    ~Raii() { 
     if (<something>) throw std::exception(); 
    } 
}; 

如果我具備的功能:

void foo() { 
    Raii raii;  

    if (something) { 
     throw std::exception(); 
    } 
} 

這是不好的,因爲在清洗的第一個例外,我們可以再次拋出,這將終止進程。

我的問題是 - 什麼是一個很好的模式使用raii代碼清理可能拋出?

例如,這是好事還是壞事 - 爲什麼?

class Raii { 
    Raii() {}; 
    ~Raii() { 
     try { 
      if (<something>) throw std::exception(); 
     } 
     catch (...) { 
      if (!std::uncaught_exception()) 
       throw; 
     } 
    } 
}; 

注意RAII對象總是棧上分配的對象 - 這是不是從析構函數的問題一般扔。

+8

這與「如何防止終止如果我有一個其析構函數可以拋出的類」的更一般情況沒有任何區別。除了「析構函數不應該拋出」以外,我認爲沒有什麼好的答案。 – sfjac

+1

沒有析構函數做一些工作,沒有RAII。我真的希望有一些事情可以做。說一些基於std :: uncaught_exception的地方,例如你可以檢測是否有異常。 – gsf

回答

7

C++幾乎肯定會有一個函數來獲取C++ 1z(如果它們按時發佈它,也稱爲C++ 17)的當前異常計數:std::uncaught_exceptions(注意複數「s」)。此外,默認情況下,析構函數聲明爲noexcept(意思是說,如果嘗試通過異常退出析構函數,則調用std::terminate)。

因此,首先,將你的析構函數標記爲throwing(noexcept(false))。接下來,跟蹤ctor中活動異常的數量,並將其與dtor中的值進行比較:如果在dtor中存在更多未捕獲的異常,則知道當前正處於堆棧展開過程中,並且再次拋出會導致調用到std::terminate

現在你決定你究竟有多特殊,以及你希望如何處理這種情況:終止程序,或者只是吞下內部異常?

一個可憐的模仿是如果uncaught_exception(單數)返回true,以不丟,但是這使得從不同的析構函數的使用,展開正試圖捕捉和處理異常觸發調用的時候,異常無法工作。該選項在當前的C++標準中可用。

+0

@kerrek已經完成C++ 17了嗎? – Yakk

+0

相關論文已被投票投入使用(N4582),並且幾乎沒有機會將其取出,現在WD已被認爲是「功能完備」的17。 –

4

the ScopeGuard article的建議是

在例外的境界,這是最基本的,如果你的「撤銷/恢復」操作失敗,你可以做什麼。您嘗試撤消操作,然後繼續操作,而不管撤消操作是否成功。

這聽起來很瘋狂,但考慮:

  1. 我設法耗盡內存,並獲得std::bad_alloc例外
  2. 我清理代碼記錄錯誤
  3. 不幸的是,寫失敗(可能磁盤已滿),並嘗試拋出異常

我可以撤銷日誌寫入嗎?我應該嘗試嗎?

當拋出異常時,您真正知道的是程序處於無效狀態。畢竟,一些不可能的事情變得可能,你不應該感到驚訝。就我個人而言,我已經看到Alexandrescu的建議最有意義的情況比其他情況更多:嘗試清理,但意識到第一個異常意味着事情已經處於無效狀態,所以額外的故障 - 特別是故障第一個問題(「錯誤級聯」) - 不應該是一個驚喜。並試圖處理他們不會結束。


我也許應該提到的是頭兒原做你提出什麼:

當頭兒原代碼可以從析構函數拋出異常,它首先檢查std::uncaught_exception()保證這是安全的。如果另一個異常已被激活,則新的異常被認爲是主異常的副作用,並且被無聲地吞下或報告在旁道上。

但是,正如Yakk所說,在C++ 11中,析構函數默認爲nothrow(true)。這意味着如果你想這樣做,你需要確保在C++ 11和更高版本中將析構函數標記爲nothrow(false)。否則,即使在飛行中沒有其他異常,從析構函數中拋出異常也會終止程序。並注意:「如果另一個異常已經激活,則新的異常被認爲是主要異常的副作用,並且無論是靜靜地吞下還是通過側通道報告。」