1.C語言傳統的處理錯誤的方式
傳統的錯誤處理機制 機製:
- 終止程式,如assert,缺陷:使用者難以接受。如發生記憶體錯誤,除0錯誤時就會終止程式。
- 返回錯誤碼,缺陷:需要程式設計師自己去查詢對應的錯誤。如系統的很多庫的介面函數都是通過把錯誤碼放到errno中,表示錯誤。
- C 標準庫中setjmp和longjmp組合。這個不是很常用,瞭解一下實際中C語言基本都是使用返回錯誤碼的方式處理錯誤,部分情況下使用終止程式處理非常嚴重的錯誤。
2. C++異常概念
異常是一種處理錯誤的方式,當一個函數發現自己無法處理的錯誤時就可以拋出異常,讓函數的直接或間接的呼叫者處理這個錯誤。
- throw: 當問題出現時,程式會拋出一個異常。這是通過使用 throw 關鍵字來完成的。
- catch: 在您想要處理問題的地方,通過例外處理程式捕獲異常.catch 關鍵字用於捕獲異常,可以有多個catch進行捕獲。
- try: try 塊中的程式碼標識將被啓用的特定異常,它後面通常跟着一個或多個 catch 塊。
如果有一個塊拋出一個異常,捕獲異常的方法會使用 try 和 catch 關鍵字。try 塊中放置可能拋出異常的程式碼,try 塊中的程式碼被稱爲保護程式碼。
```try
{
}catch( ExceptionName e1 )
{
}catch( ExceptionName e2 )
{
}catch( ExceptionName eN )
{
}
3. 異常的使用
3.1 異常的拋出和捕獲
異常的拋出和匹配原則
- 異常是通過拋出物件而引發的,該物件的型別決定了應該啓用哪個catch的處理程式碼。
- 被選中的處理程式碼是呼叫鏈中與該物件型別匹配且離拋出異常位置最近的那一個。
- 拋出異常物件後,會生成一個異常物件的拷貝,因爲拋出的異常物件可能是一個臨時物件,所以會生成一個拷貝物件,這個拷貝的臨時物件會在被catch以後銷燬。(這裏的處理類似於函數的傳值返回)
- catch(…)可以捕獲任意型別的異常,問題是不知道異常錯誤是什麼。
在函數呼叫鏈中異常棧展開匹配原則
- 首先檢查throw本身是否在try塊內部,如果是再查詢匹配的catch語句。如果有匹配的,則調到catch的地方進行處理。
- 沒有匹配的catch則退出當前函數棧,繼續在呼叫函數的棧中進行查詢匹配的catch。
- 如果到達main函數的棧,依舊沒有匹配的,則終止程式。上述這個沿着呼叫鏈查詢匹配的catch子句的過程稱爲棧展開。所以實際中我們最後都要加一個catch(…)捕獲任意型別的異常,否則當有異常沒捕獲,程式就會直接終止。
- 找到匹配的catch子句並處理以後,會繼續沿着catch子句後面繼續執行。
3.2 異常的重新拋出
有可能單個的catch不能完全處理一個異常,在進行一些校正處理以後,希望再交給更外層的呼叫鏈函數來處理,catch則可以通過重新拋出將異常傳遞給更上層的函數進行處理的。
3.3異常安全
- 建構函式完成物件的構造和初始化,最好不要在建構函式中拋出異常,否則可能導致物件不完整或沒有完全初始化
- 解構函式主要完成資源的清理,最好不要在解構函式內拋出異常,否則可能導致資源泄漏(記憶體漏失、控制代碼未關閉等)
- C++中異常經常會導致資源泄漏的問題,比如在new和delete中拋出了異常,導致記憶體漏失,在lock和unlock之間拋出了異常導致死鎖,C++經常使用RAII來解決以上問題,關於RAII我們智慧指針這節進行講解。
3.4 異常規範
- 異常規格說明的目的是爲了讓函數使用者知道該函數可能拋出的異常有哪些。 可以在函數的後面接throw(型別),列出這個函數可能拋擲的所有異常型別。
- 函數的後面接throw(),表示函數不拋異常。
- 若無異常介面宣告,則此函數可以拋擲任何型別的異常。
4.自定義異常體系
實際使用中很多公司都會自定義自己的異常體系進行規範的異常管理,因爲一個專案中如果大家隨意拋異常,那麼外層的呼叫者基本就沒辦法玩了,所以實際中都會定義一套繼承的規範體系。這樣大家拋出的都是繼承的派生類物件,捕獲一個基礎類別就可以了。
try
{
vector<double> vec;
char* ptr = new char[0x7fffffff];
}
catch (exception& e)
{
cout << e.what() << endl;
}
catch (...)
{
cout << "未知異常" << endl;
}
6.異常的優缺點
C++異常的優點:
- 異常物件定義好了,相比錯誤碼的方式可以清晰準確的展示出錯誤的各種資訊,甚至可以包含堆疊呼叫的資訊,這樣可以幫助更好的定位程式的bug。
- 返回錯誤碼的傳統方式有個很大的問題就是,在函數呼叫鏈中,深層的函數返回了錯誤,那麼我們得層層返回錯誤,最外層才能 纔能拿到錯誤,具體看下面 下麪的詳細解釋。
- 很多的第三方庫都包含異常,比如boost、gtest、gmock等等常用的庫,那麼我們使用它們也需要使用異常。
- 很多測試框架都使用異常,這樣能更好的使用單元測試等進行白盒的測試。
- 部分函數使用異常更好處理,比如建構函式沒有返回值,不方便使用錯誤碼方式處理。比如T&operator這樣的函數,如果pos越界了只能使用異常或者終止程式處理,沒辦法通過返回值表示錯誤。
C++異常的缺點:
- 異常會導致程式的執行流亂跳,並且非常的混亂,並且是執行時出錯拋異常就會亂跳。這會導致我們跟蹤偵錯時以及分析程式時,比較困難。
- 異常會有一些效能的開銷。當然在現代硬體速度很快的情況下,這個影響基本忽略不計。
- C++沒有垃圾回收機制 機製,資源需要自己管理。有了異常非常容易導致記憶體漏失、死鎖等異常安全問題。這個需要使用RAII來處理資源的管理問題。學習成本較高。
- C++標準庫的異常體系定義得不好,導致大家各自定義各自的異常體系,非常的混亂。
- 異常儘量規範使用,否則後果不堪設想,隨意拋異常,外層捕獲的使用者苦不堪言。所以異常規範有兩點:一、拋出異常型別都繼承自一個基礎類別。二、函數是否拋異常、拋什麼異常,都使用 func()throw();的方式規範化。
總結:異常總體而言,利大於弊,所以工程中我們還是鼓勵使用異常的。另外OO的語言基本都是用例外處理錯誤,這也可以看出這是大勢所趨。