C++高階程式設計 第十五章: 處理錯誤
這一章主要講了異常處理的機制, 還有異常的種類, 怎樣設計比較好, 怎樣設計使用異常處理等等.
1.到底什麼是異常
異常是一種機制: 一段程式碼發生了錯誤情況後, 就會通知另外一段程式碼. 然後程式就不會再沿著正常的程式碼路徑前進了, 遇到錯誤的程式碼會丟擲異常, 處理異常的程式碼就會捕獲這個異常 (捕獲這個動作其實是程式已經把控制權轉移給異常處理的程式了).\
為什麼要使用異常處理?
是這樣的,有關異常的C++程式設計,有三個境界:
#第一個境界就是:程式中看到不try,catch,finally。
這是新手的水平,他不知道有的模組/函式是會有異常丟擲的,不處理的話,程式會當掉,很多資源會不能及時正確回收。或者他寫程式時反覆應用errno或者檢查返回值的方式來處理異常情況,排錯程式碼和正常流程程式碼攪在一起,混亂不堪。
#第二個境界就是:程式中看到好多好多try ,catch,finally。
這是入門級的水平,他懂得利用拋異常的方式來處理錯誤情況,所以在程式中,正常的流程會統一在try裡,各種錯誤處理,都安排在catch當中,小心翼翼地做好的善後工作。有時候狠起來還使用catch(…)來強行把所有的異常都壓下來。這樣沒有什麼混亂?才怪,各種善後處理雖然都做了,但是他不知道要寫多少個try,多少個catch,而且經常要把思路放到catch當中去。
#第三個境界就是:程式中還是看到不try,catch,finally。
然而有不同,這一回他是手中無劍心有劍的高手境界了。
他知道異常安全的三個保證,並且懂得在什麼時候分別提供#資源回收保證#資料一致性保證#無異常保證。
---他會使用C++超強的RAII(資源獲取即初始化)來使得資源在產生異常時會自動回收(寫一個類就可以管理一種資源,一勞永逸,不用天天catch 來catch去)。
---他會使用pimpl技法幫助實現RAII,並把邏輯操作分派到各個成員內部當中,使之在發生異常時保持一致性。
---他另外還會常常使用一個no throw的swap操作一次性把所有的操作完成。這樣的話,物件就不會成為爛尾樓。
於是,這個高手寫的類自己不用異常來打擾你,如果真的在內部其它類發生了異常,這個異常也安全地透過這個高手的類傳到更上一層去,不破壞類本身的資料完整性。雖然異常還會有,但是,安全了~
異常的優點: 我們平常處理一個函數出錯,通常都是用這個函式的返回值判斷是否出錯,但是不同的庫有不同的函式, 有些0是錯誤的,有些則是1或者-1, 所以錯誤程式碼參次不齊,所以用統一的錯誤機制去獲取會無視錯誤返回值.另外使用異常的話,當用戶的錯誤行為導致異常, 程式會當掉,很多資源會不能及時正確回收, 而且無法儲存當前的狀態.
2. 異常到底怎麼運用
異常處理採取的做法: 先是”嘗試(try) 一個行為”, 然後在這個行為裡面,如果遇到了錯誤, 就”丟擲異常(throw) ,然後結束當前行為”,在丟擲異常後, 我們需要用一個東西”接住(catch)這個異常”,
int main(){
try{
int wait = a();
}
catch(exception &e){
cout<<"error"<<endl;
return 1;
}
}
int a(){
xxxxxxx
xxxxxxx
if(xxxx==NULL){
throw exception();
}
}
當然 我們throw的話 可以throw其他東西,
例如 throw 5…..throw “severe busy”…..throw 1.58.
異常類有提供 了很多種的異常, expetion只是積累而已,它的派生類,他們都存在於#include裡面
所以我自己就用exception &e 去接住所有種類的異常資訊.
3.未捕獲的異常
如果一個程式丟擲了一個異常,但是未在任何地方去捕獲, 這個程式就會終止.
這就違反了本意, 我們使用異常主要是讓程式有機會處理和修正不合適或意外情況.
所以我們應該捕獲丟擲可能中的所有異常.
並且我們應該編寫自己的異常類.
- 棧展開和清除
當代碼丟擲異常後, 控制會立刻跳至與異常匹配的異常處理程式裡面, 這個程式可能位於棧上,也可能位於函式上, 這個過程稱為棧展開. 當前執行點打後的程式碼就不會再執行了!!!
位於展開函式裡面的區域性變數和物件是會像函式一樣撤銷的
但是, 指標變數卻不會釋放,也不會完成其清除工作.
例如: int a(){ int *p = new int; throw exception();// delete p;}
當throw異常後,後面的delete語句是不會執行的,所以我們要小心這個特例
所以要避免記憶體洩露, 我們在做好異常處理的同時, 也要完成清除工作.
就是要設計出更好的程式碼,以免造成這個情況.!!!
4.建構函式and解構函式 的錯誤
在我們寫自己的建構函式中, 可能會用到建構函式, 如果在執行建構函式的一半,發生的異常丟擲, 那那麼之前構造的東西怎麼辦呢?
答案就是: 我們必須手動釋放我們之前分配的記憶體.
那麼當我們加了繼承之後怎麼辦呢?就是構造時, 先執行父類的建構函式,再執行子類的建構函式, 情況和上面的一樣. 在子類的構造中出現了異常. 那麼超類的怎麼釋放記憶體呢?
答案就是: 那些已經完成完整構造的類的物件, 他們的物件的解構函式會自動執行. 就是說, 只要完成了正常的建構函式那麼相對應的解構函式就會執行.
解構函式中,我們不要去採用異常處理
因為,析構是最後唯一一個方法去釋放記憶體, 如果我們在析構的過程中發生了異常丟擲, 那麼這個解構函式是不會完整執行的.也就是說, 會不經意的造成記憶體洩露.!!!