1. 程式人生 > >【C++】異常處理

【C++】異常處理

一、什麼是異常處理

        一句話:異常處理就是處理程式中的錯誤。

二、為什麼需要異常處理,以及異常處理的基本思想

        C++之父Bjarne Stroustrup在《The C++ Programming Language》中講到:一個庫的作者可以檢測出發生了執行時錯誤,但一般不知道怎樣去處理它們(因為和使用者具體的應用有關);另一方面,庫的使用者知道怎樣處理這些錯誤,但卻無法檢查它們何時發生(如果能檢測,就可以再使用者的程式碼裡處理了,不用留給庫去發現)。

        Bjarne Stroustrup說:提供異常基本目的就是為了處理上面的問題。基本思想是:讓一個函式在發現了自己無法處理的錯誤時丟擲(throw)一個異常,然後它的(直接或者間接)呼叫者能夠處理這個問題。 

The fundamental idea is that a function that finds a problem it cannot cope with throws an exception, hoping that its (direct or indirect) caller can handle the problem.

        也就是《C++ primer》中說的:將問題檢測問題處理相分離。 Exceptions let us separate problem detection from problem resolution

        一種思想:在所有支援異常處理的程式語言中(例如java),要認識到的一個思想:在異常處理過程中,由問題檢測程式碼可以丟擲一個物件給問題處理程式碼,通過這個物件的型別和內容

,實際上完成了兩個部分的通訊,通訊的內容是“出現了什麼錯誤”。當然,各種語言對異常的具體實現有著或多或少的區別,但是這個通訊的思想是不變的。

三、異常出現之前處理錯誤的方式

        在C語言的世界中,對錯誤的處理總是圍繞著兩種方法:一是使用整型的返回值標識錯誤;二是使用errno巨集(可以簡單的理解為一個全域性整型變數)去記錄錯誤。當然C++中仍然是可以用這兩種方法的。

        這兩種方法最大的缺陷就是會出現不一致問題。例如有些函式返回1表示成功,返回0表示出錯;而有些函式返回0表示成功,返回非0表示出錯。

        還有一個缺點就是函式的返回值只有一個,你通過函式的返回值表示錯誤程式碼,那麼函式就不能返回其他的值。當然,你也可以通過指標或者C++的引用來返回另外的值,但是這樣可能會令你的程式略微晦澀難懂。

四、異常為什麼好

    在如果使用異常處理的優點有以下幾點:

        1. 函式的返回值可以忽略,但異常不可忽略。如果程式出現異常,但是沒有被捕獲,程式就會終止,這多少會促使程式設計師開發出來的程式更健壯一點。而如果使用C語言的error巨集或者函式返回值,呼叫者都有可能忘記檢查,從而沒有對錯誤進行處理,結果造成程式莫名其妙的終止或出現錯誤的結果。

        2. 整型返回值沒有任何語義資訊。而異常卻包含語義資訊,有時你從類名就能夠體現出來。

        3. 整型返回值缺乏相關的上下文資訊。異常作為一個類,可以擁有自己的成員,這些成員就可以傳遞足夠的資訊。

        4. 異常處理可以在呼叫跳級。這是一個程式碼編寫時的問題:假設在有多個函式的呼叫棧中出現了某個錯誤,使用整型返回碼要求你在每一級函式中都要進行處理。而使用異常處理的棧展開機制,只需要在一處進行處理就可以了,不需要每級函式都處理。

五、C++中使用異常時應注意的問題

    任何事情都是兩面性的,異常有好處就有壞處。如果你是C++程式設計師,並且希望在你的程式碼中使用異常,那麼下面的問題是你要注意的。

        1. 效能問題。這個一般不會成為瓶頸,但是如果你編寫的是高效能或者實時性要求比較強的軟體,就需要考慮了。

(如果你像我一樣,曾經是java程式設計師,那麼下面的事情可能會讓你一時迷糊,但是沒辦法,誰叫你現在學的是C++呢。)

       2. 指標和動態分配導致的記憶體回收問題:在C++中,不會自動回收動態分配的記憶體,如果遇到異常就需要考慮是否正確的回收了記憶體。在java中,就基本不需要考慮這個,有垃圾回收機制真好!

        3. 函式的異常丟擲列表:java中是如果一個函式沒有在異常丟擲列表中顯式指定要丟擲的異常,就不允許丟擲;可是在C++中是如果你沒有在函式的異常丟擲列表指定要丟擲的異常,意味著你可以丟擲任何異常

        4. C++中編譯時不會檢查函式的異常丟擲列表。這意味著你在編寫C++程式時,如果在函式中丟擲了沒有在異常丟擲列表中宣告的異常,編譯時是不會報錯的。而在java中,eclipse的提示功能真的好強大啊!

        5. 在java中,丟擲的異常都要是一個異常類;但是在C++中,你可以丟擲任何型別,你甚至可以丟擲一個整型。(當然,在C++中如果你catch中接收時使用的是物件,而不是引用的話,那麼你丟擲的物件必須要是能夠複製的。這是語言的要求,不是異常處理的要求)。

        6. 在C++中是沒有finally關鍵字的。而java和python中都是有finally關鍵字的。

六、異常的基本語法

1. 丟擲和捕獲異常

        很簡單,丟擲異常用throw,捕獲用try……catch

        捕獲異常時的注意事項:

             1. catch子句中的異常說明符必須是完全型別,不可以為前置宣告,因為你的異常處理中常常要訪問異常類的成員。例外:只有你的catch子句使用指標或者引用接收引數,並且在catch子句內你不訪問異常類的成員,那麼你的catch子句的異常說明符才可以是前置宣告的型別。

             2. catch的匹配過程是找最先匹配的,不是最佳匹配。

             3. catch的匹配過程中,對型別的要求比較嚴格允許標準算術轉換類型別的轉換。(類型別的轉化包括種:通過建構函式的隱式型別轉化和通過轉化操作符的型別轉化)。

             4. 和函式引數相同的地方有:                      ① 如果catch中使用基類物件接收子類物件,那麼會造成子類物件分隔slice)為父類子物件(通過呼叫父類的複製建構函式);                      ② 如果catch中使用基類物件的引用接受子類物件,那麼對虛成員的訪問時,會發生動態繫結,即會多型呼叫。                      ③ 如果catch中使用基類物件的指標,那麼一定要保證throw語句也要丟擲指標型別,並且該指標所指向的物件,在catch語句執行是還存在(通常是動態分配的物件指標)。

             5. 和函式引數不同的地方有:                        ① 如果throw中丟擲一個物件,那麼無論是catch中使用什麼接收(基類物件、引用、指標或者子類物件、引用、指標),在傳遞到catch之前,編譯器都會另外構造一個物件的副本。也就是說,如果你以一個throw語句中丟擲一個物件型別,在catch處通過也是通過一個物件接收,那麼該物件經歷了兩次複製,即呼叫了兩次複製建構函式。一次是在throw時,將“丟擲到物件”複製到一個“臨時物件”(這一步是必須的),然後是因為catch處使用物件接收,那麼需要再從“臨時物件”複製到“catch的形參變數”中; 如果你在catch中使用“引用”來接收引數,那麼不需要第二次複製,即形參的引用指向臨時變數。                      ② 該物件的型別與throw語句中體現的靜態型別相同。也就是說,如果你在throw語句中丟擲一個指向子類物件的父類引用,那麼會發生分割現象,即只有子類物件中的父類部分會被丟擲,丟擲物件的型別也是父類型別。(從實現上講,是因為複製到“臨時物件”的時候,使用的是throw語句中型別的(這裡是父類的)複製建構函式)。                      ③ 不可以進行標準算術轉換類的自定義轉換:在函式引數匹配的過程中,可以進行很多的型別轉換。但是在異常匹配的過程中,轉換的規則要嚴厲。

                    ④ 異常處理機制的匹配過程是尋找最先匹配(first fit),函式呼叫的過程是尋找最佳匹配(best fit)。

2. 異常型別

        上面已經提到過,在C++中,你可以丟擲任何型別的異常。(哎,竟然可以丟擲任何型別,剛看到到這個的時候,我半天沒反應過來,因為java中這樣是不行的啊)。

         注意:也是上面提到過的,在C++中如果你throw語句中丟擲一個物件,那麼你丟擲的物件必須要是能夠複製的。因為要進行復制副本傳遞,這是語言的要求,不是異常處理的要求。(在上面“和函式引數不同的地方”中也講到了,因為是要複製先到一個臨時變數中)

3. 棧展開

        棧展開指的是:當異常丟擲後,匹配catch的過程

        丟擲異常時,將暫停當前函式的執行,開始查詢匹配的catch子句。沿著函式的巢狀呼叫鏈向上查詢,直到找到一個匹配的catch子句,或者找不到匹配的catch子句。

        注意事項:

               1. 在棧展開期間,會銷燬區域性物件。

                     ① 如果區域性物件是類物件,那麼通過呼叫它的解構函式銷燬。

                     ② 但是對於通過動態分配得到的物件,編譯器不會自動刪除,所以我們必須手動顯式刪除。(這個問題是如此的常見和重要,以至於會用到一種叫做RAII的方法,詳情見下面講述)

               2. 解構函式應該從不丟擲異常。如果解構函式中需要執行可能會丟擲異常的程式碼,那麼就應該在解構函式內部將這個異常進行處理,而不是將異常丟擲去。

                     原因:在為某個異常進行棧展開時,解構函式如果又丟擲自己的未經處理另一個異常,將會導致呼叫標準庫 terminate 函式。而預設的terminate 函式將呼叫 abort 函式,強制從整個程式非正常退出。

               3. 建構函式中可以丟擲異常。但是要注意到:如果建構函式因為異常而退出,那麼該類的解構函式就得不到執行。所以要手動銷燬在異常丟擲前已經構造的部分。

4. 異常重新丟擲

        語法:使用一個空的throw語句。即寫成: throw;  

        注意問題:

                ① throw;  語句出現的位置,只能是catch子句中或者是catch子句呼叫的函式中。                  ② 重新丟擲的是原來的異常物件,即上面提到的“臨時變數”,不是catch形參。                  ③ 如果希望在重新丟擲之前修改異常物件,那麼應該在catch中使用引用引數。如果使用物件接收的話,那麼修改異常物件以後,不能通過“重新丟擲”來傳播修改的異常物件,因為重新丟擲不是catch形參,應該使用的是 throw e;  這裡“e”為catch語句中接收的物件引數。

5. 捕獲所有異常(匹配任何異常)

        語法:在catch語句中,使用三個點(…)。即寫成:catch (…)   這裡三個點是“萬用字元”,類似 可變長形式引數。

        常見用法:與“重新丟擲”表示式一起使用,在catch中完成部分工作,然後重新丟擲異常。

6. 未捕獲的異常

        意思是說,如果程式中有丟擲異常的地方,那麼就一定要對其進行捕獲處理。否則,如果程式執行過程中丟擲了一個異常,而又沒有找到相應的catch語句,那麼會和“棧展開過程中解構函式丟擲異常”一樣,會 呼叫terminate 函式,而預設的terminate 函式將呼叫 abort 函式,強制從整個程式非正常退出。

7. 建構函式的函式測試塊

        對於在建構函式的初始化列表中丟擲的異常,必須使用函式測試塊(function try block)來進行捕捉。語法型別下面的形式:

MyClass::MyClass(int i) 
try :member(i) 
{ 
    //函式體 
} 
catch(異常引數) 
{ 
    //異常處理程式碼 
}

注意事項:在函式測試塊中捕獲的異常,在catch語句中可以執行一個記憶體釋放操作,然後異常仍然會再次丟擲到使用者程式碼中。

8. 異常丟擲列表(異常說明 exception specification)

        就是在函式的形參表之後(如果是const成員函式,那麼在const之後),使用關鍵字throw宣告一個帶著括號的、可能為空的 異常型別列表。形如:throw ()  或者 throw (runtime_error, bad_alloc)   。

        含義:表示該函式只能丟擲 在列表中的異常型別。例如:throw() 表示不丟擲任何異常。而throw (runtime_error, bad_alloc)表示只能丟擲runtime_error 或bad_alloc兩種異常。

        注意事項:(以前學java的尤其要注意,和java中不太一樣)

                ① 如果函式沒有顯式的宣告 丟擲列表,表示異常可以丟擲任意列表。(在java中,如果沒有異常丟擲列表,那麼是不能丟擲任何異常的)。

                ② C++的 “throw()”相當於java的不宣告丟擲列表。都表示不丟擲任何異常。

                ③ 在C++中,編譯的時候,編譯器不會對異常丟擲列表進行檢查。也就是說,如果你聲明瞭丟擲列表,即使你的函式程式碼中丟擲了沒有在丟擲列表中指定的異常,你的程式依然可以通過編譯,到執行時才會出錯,對於這樣的異常,在C++中稱為“意外異常”(unexpeced exception)。(這點和java又不相同,在java中,是要進行嚴格的檢查的)。

        意外異常的處理:                  如果程式中出現了意外異常,那麼程式就會呼叫函式unexpected()。這個函式的預設實現是呼叫terminate函式,即預設最終會終止程式。

        虛擬函式過載方法時異常丟擲列表的限制                  在子類中過載時,函式的異常說明 必須要比父類中要同樣嚴格,或者更嚴格。換句話說,在子類中相應函式的異常說明不能增加新的異常。或者再換句話說:父類中異常丟擲列表是該虛擬函式的子類過載版本可以丟擲異常列表的 超集

        函式指標中異常丟擲列表的限制                   異常丟擲列表是函式型別的一部分,在函式指標中也可以指定異常丟擲列表。但是在函式指標初始化或者賦值時,除了要檢查返回值形式引數外,還要注意異常丟擲列表的限制:源指標的異常說明必須至少和目標指標的一樣嚴格。比較拗口,換句話說,就是宣告函式指標時指定的異常丟擲列表,一定要實際函式的異常丟擲列表的超集。 如果定義函式指標時不提供異常丟擲列表,那麼可以指向能夠丟擲任意型別異常的函式。               

        丟擲列表是否有用                     在《More effective C++》第14條,Scott Meyers指出“要謹慎的使用異常說明”(Use exception specifications judiciously)。“異常說明”,就是我們所有的“異常丟擲列表”。之所以要謹慎,根本原因是因為C++編譯器不會檢查異常丟擲列表,這樣就可能在函式程式碼中、或者呼叫的函式中丟擲了沒有在丟擲列表中指定的異常,從而導致程式呼叫unexpected函式,造成程式提前終止。同時他給出了三條要考慮的事情:                           ① 在模板不要使用異常丟擲列表。(原因很簡單,連用來例項模板的型別都不知道,也就無法確定該函式是否應該丟擲異常,丟擲什麼異常)。                            ② 如果A函式內呼叫了B函式,而B函式沒有宣告異常丟擲列表,那麼A函式本身也不應該設定異常丟擲列表。(原因是,B函式可能丟擲沒有在A函式的異常丟擲列表中宣告的異常,會導致呼叫unex函式);                           ③ 通過set_unexpected函式指定一個新的unexpected函式,在該函式中捕獲異常,並丟擲一個統一型別的異常。

                 另外,在《C++ Primer》4th 中指出,雖然異常說明應用有限,但是如果能夠確定該函式不會丟擲異常,那麼顯式宣告其不丟擲任何異常有好處。通過語句:"throw ()"。這樣的好處是:對於程式設計師,當呼叫這樣的函式時,不需要擔心異常。對於編譯器,可以執行被可能丟擲異常所抑制的優化。

七、標準庫中的異常類

        和java一樣,標準庫中也提供了很多的異常類,它們是通過類繼承組織起來的。標準異常被組織成八個

        異常類繼承層級結構圖如下: 

每個類所在的標頭檔案在圖下方標識出來.

標準異常類的成員:          ① 在上述繼承體系中,每個類都有提供了建構函式、複製建構函式、和賦值操作符過載。          ② logic_error類及其子類、runtime_error類及其子類,它們的建構函式是接受一個string型別的形式引數,用於異常資訊的描述;          ③ 所有的異常類都有一個what()方法,返回const char* 型別(C風格字串)的值,描述異常資訊。

標準異常類的具體描述: 

異常名稱

描述

exception 所有標準異常類的父類
bad_alloc 當operator new and operator new[],請求分配記憶體失敗時
bad_exception 這是個特殊的異常,如果函式的異常丟擲列表裡聲明瞭bad_exception異常,當函式內部丟擲了異常丟擲列表中沒有的異常,這是呼叫的unexpected函式中若丟擲異常,不論什麼型別,都會被替換為bad_exception型別
bad_typeid 使用typeid操作符,操作一個NULL指標,而該指標是帶有虛擬函式的類,這時丟擲bad_typeid異常
bad_cast 使用dynamic_cast轉換引用失敗的時候
ios_base::failure io操作過程出現錯誤
logic_error 邏輯錯誤,可以在執行前檢測的錯誤
runtime_error 執行時錯誤,僅在執行時才可以檢測的錯誤

logic_error的子類: 

異常名稱

描述

length_error 試圖生成一個超出該型別最大長度的物件時,例如vector的resize操作
domain_error 引數的值域錯誤,主要用在數學函式中。例如使用一個負值呼叫只能操作非負數的函式
out_of_range 超出有效範圍
invalid_argument 引數不合適。在標準庫中,當利用string物件構造bitset時,而string中的字元不是’0’或’1’的時候,丟擲該異常

  runtime_error的子類: 

異常名稱

描述

range_error 計算結果超出了有意義的值域範圍
overflow_error 算術計算上溢
underflow_error 算術計算下溢

八、編寫自己的異常類

        1. 為什麼要編寫自己的異常類?                  ① 標準庫中的異常是有限的;                  ② 在自己的異常類中,可以新增自己的資訊。(標準庫中的異常類值允許設定一個用來描述異常的字串)。

        2. 如何編寫自己的異常類?                  ① 建議自己的異常類要繼承標準異常類。因為C++中可以丟擲任何型別的異常,所以我們的異常類可以不繼承自標準異常,但是這樣可能會導致程式混亂,尤其是當我們多人協同開發時。                  ② 當繼承標準異常類時,應該過載父類的what函式虛解構函式。                  ③ 因為棧展開的過程中,要複製異常型別,那麼要根據你在類中新增的成員考慮是否提供自己的複製建構函式

九、用類來封裝資源分配和釋放

        為什麼要使用類來封裝資源分配和釋放?                   為了防止記憶體洩露。因為在函式中發生異常,那麼對於動態分配的資源,就不會自動釋放,必須要手動顯式釋放,否則就會記憶體洩露。而對於類物件,會自動呼叫其解構函式。如果我們在解構函式中顯式delete這些資源,就能保證這些動態分配的資源會被釋放。

        如何編寫這樣的類?                   將資源的分配和銷燬用類封轉起來。在解構函式中要顯式的釋放(delete或delete[])這些資源。這樣,若使用者程式碼中發生異常,當作用域結束時,會呼叫給該類的解構函式釋放資源。這種技術被稱為:資源分配即初始化。(resource allocation is initialization,縮寫為"RAII")。

十、auto_ptr的使用(非常重要)

        “用類封裝資源的分配和釋放”是如此的重要,C++標準庫為我們提供了一個模板類來實現這個功能。名稱為auto_ptr,在memory標頭檔案中。

        auto_ptr類的成員如下:(摘自《C++ Primer》) 

函式

功能

auto_ptr <T> ap() 預設建構函式,建立名為ap的未繫結的auto_ptr物件
auto_ptr<T> ap(p); 建立名為 ap 的 auto_ptr 物件,ap 擁有指標 p 指向的物件。該建構函式為 explicit
auto_ptr<T> ap1(ap2); 建立名為 ap1 的 auto_ptr 物件,ap1 儲存原來儲存在 ap2 中的指標。將所有權轉給 ap1,ap2 成為未繫結的 auto_ptr 物件
ap1 = ap2 將所有權 ap2 轉給 ap1。刪除 ap1 指向的物件並且使 ap1 指向 ap2 指向的物件,使 ap2 成為未繫結
~ap 解構函式。刪除 ap 指向的物件
*ap 返回對 ap 所繫結的物件的引用
ap-> 返回 ap 儲存的指標
ap.reset(p) 如果 p 與 ap 的值不同,則刪除 ap 指向的物件並且將 ap 繫結到 p
ap.release() 返回 ap 所儲存的指標並且使 ap 成為未繫結的
ap.get() 返回 ap 儲存的指標

        auto_ptr類的使用:                  1. 用來儲存一個指向物件型別的指標。注意必須是動態分配的物件(即使用new非配的)的指標。既不能是動態分配的陣列(使用new [])指標,也不能是非動態分配的物件指標。                  2. 慣用的初始化方法:在使用者程式碼中,使用new表示式作為auto_ptr建構函式的引數。(注意:auto_ptr類接受指標引數的建構函式為explicit,所以必須顯式的進行初始化)。                  3. auto_ptr的行為特徵:類似普通指標行為。auto_ptr存在的主要原因就是,為了防止動態分配的物件指標造成的記憶體洩露,既然是指標,其具有"*"操作符和"->"操作符。所以auto_ptr的主要目的就是:首先保證自動刪除auto_ptr所引用的物件,並且要支援普通指標行為。                  4. auto_ptr物件的複製和賦值是有破壞性的。① 會導致右運算元成為未繫結的,導致auto_ptr物件不能放到容器中;② 在賦值的時候,將有操作符修改為未繫結,即修改了右運算元,所以要保證這裡的賦值操作符右運算元是可以修改的左值(然而普通的賦值操作符中,右運算元可以不是左值);③和普通的賦值操作符一樣,如果是自我賦值,那麼沒有效果;④ 導致auto_ptr物件不能放到容器中。                  5. 如果auto_ptr初始化的時候,使用預設建構函式,成為未繫結的auto_ptr物件,那麼可以通過reset操作將其繫結到一個物件。                  6. 如果希望測試auto_ptr是否已經繫結到了一個物件,那麼使用get()函式的返回值與NULL進行比較。

       auto_ptr的缺陷:                  1. 不能使用auto_ptr物件儲存指向靜態分配的物件的指標,也不能儲存指向動態分配的陣列的指標。                  2. 不能講兩個auto_ptr物件指向同一個物件。因為在一個auto_ptr物件析構以後,造成另一個auto_ptr物件指向了已經釋放的記憶體。造成這種情況的兩種主要常見原因是:① 用同一個指標初始化或者reset兩個不同的auto_ptr物件;② 使用一個auto_ptr物件的get函式返回值初始化或者reset另一個auto_ptr物件。                  3. 不能將auto_ptr物件放到容器中。因為其複製和賦值操作具有破壞性。

十一、常見的異常處理問題

    動態記憶體分配錯誤

         ① 分配動態記憶體使用的是new和new[]操作符,如果他們分配記憶體失敗,就會丟擲bad_alloc異常,在new標頭檔案中,所以我們的程式碼中應該捕捉這些異常。常見的程式碼形式如下:

try 
{ 
    //其他程式碼 
    ptr = new int[num_max]; 
    //其他程式碼 
} 
catch(bad_alloc &e) 
{ 
    //這裡常見的處理方式為:先釋放已經分配的記憶體,然後結束程式,或者列印一條錯誤資訊並繼續執行 
} 

 ② 可以使用類似C語言的方式處理,但這時要使用的nothrow版本,使用"new (nothrow)"的形式分配記憶體。這時,如果分配不成功,返回的是NULL指標,而不再是丟擲bad_alloc異常。           ③ 可以定製記憶體分配失敗行為。C++允許指定一個new 處理程式(newhandler)回撥函式。預設的並沒有new 處理程式,如果我們設定了new 處理程式,那麼當new和new[] 分配記憶體失敗時,會呼叫我們設定的new 處理程式,而不是直接丟擲異常。通過set_new_handler函式來設定該回調函式。要求被回撥的函式沒有返回值,也沒有形式引數

十二、來自C++之父Bjarne Stroustrup的建議

    節選自《The C++ Programming Language》 ——C++之父Bjarne Stroustrup           1. Don’t use exceptions where more local control structures will suffice;    當局部的控制能夠處理時,不要使用異常;           2. Use the "resource allocation is initialization" technique to manage resources;   使用“資源分配即初始化”技術去管理資源;           3. Minimize the use of try-blocks. Use "resource acquisition is initialization" instead of explicit handler code;    儘量少用try-catch語句塊,而是使用“資源分配即初始化”技術。           4. Throw an exception to indicate failure in a constructor;     如果建構函式內發生錯誤,通過丟擲異常來指明。           5. Avoid throwing exceptions from destructors;     避免在解構函式中丟擲異常。           6. Keep ordinary code and error-handling code separate;      保持普通程式程式碼和異常處理程式碼分開。           7. Beware of memory leaks caused by memory allocated by new not being released in case of an exception;  小心通過new分配的記憶體在發生異常時,可能造成記憶體洩露。           8. Assume that every exception that can be thrown by a function will be thrown;    如果一個函式可能丟擲某種異常,那麼我們呼叫它時,就要假定它一定會丟擲該異常,即要進行處理。           9. Don't assume that every exception is derived from class exception;     要記住,不是所有的異常都繼承自exception類。           10. A library shouldn't unilaterally terminate a program. Instead, throw an exception and let a caller decide;    編寫的供別人呼叫的程式庫,不應該結束程式,而應該通過丟擲異常,讓呼叫者決定如何處理(因為呼叫者必須要處理丟擲的異常)。           11. Develop an error-handling strategy early in a design;    若開發一個專案,那麼在設計階段就要確定“錯誤處理的策略”。 

引用來源: