異常,讓一個函數可以在發現自己無法處理的錯誤時拋出一個異常,希望它的調用者可以直接或者間接處理這個問題。而傳統錯誤處理技術,檢查到一個局部無法處理的問題時:
1.終止程序(例如atol,atoi,輸入NULL,會產生段錯誤,導致程序異常退出,如果沒有core文件,找問題的人一定會發瘋)
2.返回一個表示錯誤的值(很多系統函數都是這樣,例如malloc,內存不足,分配失敗,返回NULL指針)
3.返回一個合法值,讓程序處於某種非法的狀態(最坑爹的東西,有些第三方庫真會這樣)
4.調用一個預先準備好在出現"錯誤"的情況下用的函數。
第一種情況是不允許的,無條件終止程序的庫無法運用到不能當機的程序裏。第二種情況,比較常用,但是有時不合適,例如返回錯誤碼是int,每個調用都要檢查錯誤值,極不方便,也容易讓程序規模加倍(但是要精確控制邏輯,我覺得這種方式不錯)。第三種情況,很容易誤導調用者,萬一調用者沒有去檢查全局變量errno或者通過其他方式檢查錯誤,那是一個災難,而且這種方式在併發的情況下不能很好工作。至於第四種情況,本人覺得比較少用,而且回調的代碼不該多出現。
使用異常,就把錯誤和處理分開來,由庫函數拋出異常,由調用者捕獲這個異常,調用者就可以知道程序函數庫調用出現錯誤了,並去處理,而是否終止程序就把握在調用者手裏了。
但是,錯誤的處理依然是一件很困難的事情,C++的異常機制爲程序員提供了一種處理錯誤的方式,使程序員可以更自然的方式處理錯誤。
異常的描述
函數和函數可能拋出的異常集合作爲函數聲明的一部分是有價值的,例如
void f(int a) throw (x2,x3);
表示f()只能拋出兩個異常x2,x3,以及這些類型派生的異常,但不會拋出其他異常。如果f函數違反了這個規定,拋出了x2,x3之外的異常,例如x4,那麼當函數f拋出x4異常時,
會轉換爲一個std::unexpected()調用,默認是調用std::terminate(),通常是調用abort()。
如果函數不帶異常描述,那麼假定他可能拋出任何異常。例如:
int f(); //可能拋出任何異常
不帶任何異常的函數可以用空表表示:
int g() throw (); // 不會拋出任何異常
捕獲異常
捕獲異常的代碼一般如下:
try { throw E(); } catch (H h) { //何時我們可以能到這裏呢 }
1.如果H和E是相同的類型
2.如果H是E的基類
3.如果H和E都是指針類型,而且1或者2對它們所引用的類型成立
4.如果H和E都是引用類型,而且1或者2對H所引用的類型成立
從原則上來說,異常在拋出時被複制,我們最後捕獲的異常只是原始異常的一個副本,所以我們不應該拋出一個不允許拋出一個不允許複製的異常。
此外,我們可以在用於捕獲異常的類型加上const,就像我們可以給函數加上const一樣,限制我們,不能去修改捕捉到的那個異常。
還有,捕獲異常時如果H和E不是引用類型或者指針類型,而且H是E的基類,那麼h對象其實就是H h = E(),最後捕獲的異常對象h會丟失E的附加攜帶信息。