1. 程式人生 > >《More Effective C++ 》讀書筆記(二)Exception 異常

《More Effective C++ 》讀書筆記(二)Exception 異常

derived 對象 模板 帶來 成員 臨時對象 行為 ron 阻止

這事篇讀書筆記,只記錄自己的理解和總結,一般情況不對其舉例子具體說明,因為那正是書本身做的事情,我的筆記作為梳理和復習之用,劃重點。我推薦學C++的人都好好讀一遍Effective C++ 系列,真是好書啊,對於學完C++ 基礎知識的人,這是本高階秘籍。

筆記

條款 9 - 15 關註的主題是異常。關註1、異常可能引起的資源泄露(強烈推薦使用智能指針)2、異常是如何拋出的和3、異常的成本。目的是寫出 exception-safe 的程序。

  • 條款9: 利用 destructor避免資源泄露
    這個條款是說如果異常被拋出到調用端,但是調用端沒有catch, 如果調用端是指針,用來析構指針的delete語句可能因為異常被拋出而跳過,導致資源泄露。最好的辦法就是不用指針,用臨時對象,或者說用行為類似指針的臨時對象(為什麽非要用類似指針的行為呢?為了多態),臨時對象會在對象生存期結束的時候析構,在析構函數中delete(如果有指針的話) 釋放資源。這裏的原則就是所謂RAII(Resource Acquisition Is Initialization),在具體實踐上就是使用智能指針,C++11 中提供了std::shared_ptr 和 std::unique_ptr,目的就是為了避免資源泄露。

  • 條款10:在 constructor 內阻止資源泄露。 這個條款舉例子說明了如果在對象構造時發生了異常,這個時候因為構造未完成故不會調用該對象的析構函數,該對象的指針成員面臨著沒有delete 的風險,導致資源泄露。解決辦法跟條款9一樣,想用指針的時候使用智能指針。

  • 條款11:禁止異常流出destructors 之外。 這個條款是要說明,如果異常發生在destuctor中,不要將其拋出。 理由1是因為如果這個destuctor是因為某個異常而調用的,在這時候再次發生異常並拋出會導致程序 terminate,程序將會終結,也就是最不喜歡看到的程序崩了。處理方式是把異常給吞了,如下面的例子。理由2是如果異常被拋出,引起異常的函數後面的代碼都沒有被執行,析構函數沒有析構完。因此要盡力阻止異常被destrucor 拋出。
~AClass(){
    try{
        a_function_may_throw_exception();
    }catch(...){
        // done nothing.
    }
}
  • 條款12:了解“拋出一個異常excption” 與“傳遞一個參數”或者“調用一個虛函數”之間的差異。 首先得明確一點, 拋出異常,事實上也是拋出異常對象,看上去跟傳遞對象參數是很類似的。(1) 但是,異常在拋出的時候總是伴隨著對象copy行為(想想這是為什麽,提示與局部對象生存期有關),也因此在效率上發生折扣。常見參數的傳遞的方式有 by value, by reference, by pointer. 不管是 by value 還是 by reference 拋出異常實例都會引起復制,捕捉異常時 by value 還要多復制一次。 throw by pointer 和 pass by pointer。 都是傳遞指針的副本,千萬不要傳遞一個指向局部對象的指針
    ,否則會獲得一個指向已銷毀的對象的指針。(2) 傳遞exception 不會發生隱式類型轉化,除非是有型指針轉為 void* 或者是繼承架構中的轉化(catch base 的可以catch derived 的異常),catch 語句中遵循 first fit, 即第一個匹配的就被捕捉,所以 base exception 會攔截 derived excetion。(因此應該讓catch derived excaption 的語句放在前面)。
  • 條款13:以 by reference 的方式捕捉異常。 無論如何不要用 by pointer 方式捕捉異常; by value 傳遞會有slice 問題而且比 by reference 多復制一次。綜合條款 12 和13 ,結論就是使用 by reference 的方式捕捉異常。
  • 條款14:明智運用exception specifications。 這是一把雙刃劍,一方面對於函數希望提供什麽樣的exception 提供了說明,另外也帶來了不安全的行為。unexcepted 函數默認終結程序,如果違反了 exception specification 會調用unexception函數。記住1.不要和模板 template 混用;2.不要對回調函數使用exception specification.

    Note:
    1. void foo() throw(); // 含義是不拋出異常
    2. void foo() throw(A,B,C); // 含義是可能拋出A,B,C 型異常
    3. 書中76 頁中介紹了 set_unexpected()函數和將非預期的異常轉化為已知異常的技術,可以較為安全的處理unexpected 發生。
  • 條款15:了解異常處理(excption handling)的成本。 成本主要是三點:1.為了支持異常,程序需要做大量簿記工作,消耗額外的內存和時間,而且因為只要程序庫或者用戶代碼任何一處用了異常處理,編譯器就必須提供對異常處理的支持能力。 2. 編譯器實現try語句塊和 excpetion specification 帶來的代碼膨脹和性能損失,2000年之前作者寫這本書的時候,得到的消息和測試結果是 5%-10%的損失,不知道現在誰否有提升。 3.拋出異常的損失,可能比正常的函數返回要慢三個數量級。註意,這個只是在發生異常並拋出的時候才有,而異常應該是比較少發生才是。

總結

  1. 為了異常安全,使用智能指針;
  2. 析構函數不要拋出異常,應該吞掉;
  3. 捕捉catch 異常應該 by reference;
  4. 異常處理是有成本的。(迷思:這些成本如今是否得到優化,或者項目可以容忍?如果我開始一個新項目,我要不要使用異常處理?)

《More Effective C++ 》讀書筆記(二)Exception 異常