《More Effective C++》總結筆記(二)——異常

異常

條款9:利用destructors避免泄露資源

  • 只要堅持這個規則,把資源封裝在對象內(類似智能指針shared_ptr),通常便可以在exceptions出現時避免泄露資源。
  • 簡單來說就是,當有資源可能在函數拋異常時而無法釋放,這時可以將資源封裝到對象內(RAII),利用對象的析構函數來自動釋放資源,這樣即使有exceptions發生,也不會有資源泄露。

條款10:在constructors內阻止資源泄露(resource leak)

  • C++只會析構已構造完成的對象。對象只有在其constructor執行完畢纔算是完成構造妥當。
  • 由於C++不自動清理那些“構造期間拋出exceptions”的對象,所以你必須設計你的constructors,使它們在那種情況下亦能自我清理。通常這隻需要將所有可能的exceptions捕捉起來,執行某種清理工作,然後重新拋出exception,使它繼續傳播出去即可。
  • 但是,更優雅的做法實際上是將這些需要在constructor內初始化的對象視爲資源,將它們交由智能指針來管理。
  • 結論是:如果你以auto_ptr(C++11後使用shared_ptr或者unique_ptr)對象來取代pointer class members,你便對你的constructors做了強化工事,免除了“exceptions出現時發生資源泄露”的危機,不再需要在destructors內親自動手釋放資源,並允許const member pointers得以和non-const member pointers有着一樣優雅的處理方式。

條款11:禁止異常(exceptions)流出destructors之外

Session::Session()
{
  try {
    logDestruction(this);
  }
  catch (...) {
  }
}
  • 這裏的catch語句塊看起來什麼都沒做,但是外表容易騙人。這個語句塊阻止了“logDestruction所拋出的exceptions”傳出Session destructor之外。
  • 有兩個好理由支持我們“全力阻止exceptions傳出destructors之外”。第一,它可以避免terminate函數在exception傳播過程的棧展開(stack-unwinding)機制中被調用;第二,它可以協助確保destructors完成其應該完成的所有事情。

條款12:瞭解“拋出一個exception”與“傳遞一個參數”或“調用一個虛函數”之間的差異

  • “拋出exception”與“傳遞參數”的相同點是:它們的傳遞方式有3中:by value(傳值),by reference(傳引用),by pointer(傳指針)。然而視你所傳遞的是參數或exceptions,發生的事情可能完全不同。原因是當你調用一個函數,控制權最終會回到調用端(除非函數失敗以至於無法返回),但是當你拋出一個exception,控制權不會再回到拋出端。
  • “拋出exception”與“傳遞參數”的區別一:C++特別聲明,一個對象被拋出作爲exception時,總是會發生複製(copy)。即使catch語句參數時by reference,或者拋出對象聲明爲static也一樣會發生複製。且複製動作永遠是以對象的靜態類型爲本。
  • “exception objects必定會造成複製行爲”這一事實也解釋了“傳遞參數”和“拋出exception”之間的另一個不同:後者常常比前者慢。
  • 一般而言,你必須使用一下語句:
    throw;
    才能重新拋出當前的exception,其間沒有機會讓你改變被傳播的exception的類型。此外,它也比較有效率,因爲不需要產生新的exception object。
  • “拋出exception”與“傳遞參數”的區別二:函數調用過程中將一個臨時對象傳遞給一個non-const reference參數時不允許的,但對exception則屬合法。
  • “拋出exception”與“傳遞參數”的區別三:一般而言,調用函數傳遞參數過程中允許的隱式轉換在“exceptions與catch子句相匹配”的過程中使不會發生的。
  • “exceptions與catch子句相匹配”的過程中,僅有兩種轉換可以發生。第一種是“繼承架構中的類轉換”。第二種是從一個“有型指針”轉爲“無型指針”(所以一個參數爲const void*的catch子句,可捕捉任何指針類型的exception)。
  • “拋出exception”與“傳遞參數”的區別四:catch子句總是依出現順序做匹配嘗試。與虛函數調用比較,虛函數採用”best fit“(最佳吻合)策略,而exception處理機制採用”first fit“(最先吻合)策略。因此,絕對不要將”針對base class而設計的catch子句“放在”針對derived class而設計的catch子句“之前。

條款13:以by reference方式捕捉exceptions

  • 相比於by reference方式,以by value方式捕獲exception,會使被傳遞的對象發生兩次複製,產生兩個副本。其中一個構造動作用於“任何exceptions都會產生的臨時對象”身上,另一個構造動作用於“將臨時對象複製到catch的參數上”。
  • 千萬不要拋出一個指向局部對象的指針,因爲該局部對象會在exception傳離其scope時被銷燬,因此catch子句會獲得一個指向“已被銷燬的對象”的指針。
  • 如果catch by reference,你就可以避開對象刪除問題(by pointer會面對的)——它會讓你動輒得咎,做也不是,不做也不是;你也可以避開exception objects的切割問題(派生類對象的exception objects被捕捉並被視爲基類對象的exception objects,將失去其派生成分。對象切割問題是由於靜態編聯導致的,使用引用和指針時不會發生);你可以保留捕捉C++標準exceptions的能力;你也約束了exception objects需被複制的次數。

條款14:明智運用exception specifications

  • exception specification示例,一個只拋出int類型exceptions的函數聲明爲:
void fun() throw(int);
  • 如果函數拋出一個並未列入exception specification的exception,這個錯誤會在運行時期被檢驗出來,於是特殊函數unexpected會被自動調用。unexpected的默認行爲是調用terminate。
  • 想要避免這種unexpected的方法就是:
    • 不應該將templates和exception specifications混合使用。
    • 如果A函數內調用了B函數,而B函數無exception specifications,那麼A函數本身也不要設定exception specifications。
    • 處理“系統”可能拋出的exceptions(如bad_alloc)。
  • C++允許你以不同類型的exceptions取代非預期的exceptions。如果非預期函數的替代者重新拋出當前的exception,該exception會被標準類型bad_exception取而代之。
void convertUnexpected()
{
  throw;
}

set_unexpected(convertUnexpected);
  • 如果你做了上述安排,並且每一個exception specifications都含有bad_exception,或者其基類,你就再也不必擔心程序會於是非預期的exception時中止執行。

瞭解異常處理(exception handling)的成本

  • 爲了讓exception的相關成本最小化,只要能夠不支持exceptions,編譯器便不支持;請將你對try語句塊和exception specifications的使用限制於非用不可的地點,並且在真正異常的情況下才拋出exceptions。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章