1.異常出現的目的
在c++語言的設計和演化中,Bjarne Stroustrup說過異常的設計假定如下情況:
基本上是為了處理錯誤
與函數定義相比,異常處理是很少的
與函數調用相比,異常出現的頻率較少
異常僅僅是語言層次上的概念
同時:
異常不是為了作為另外一種返回機制,而是一種容錯機制
不是想把函數都轉變成一個容錯的試題,而是想作為一種機制,提供給子系統容錯能力,即使各個函數在寫法上沒有關心全局的錯誤處理
並不是想將設計者都約束到一個正確的錯誤處理概念上,而是希望語言更有表達能力
所以說異常實際上最早出現我想更多是c++對c一種容錯機制的補充,而不是對於返回值的改進,現在很多書上都提倡用異常機制替代返回值,宣稱異常機制有很多優點,也有很多人鄙視異常機制,比如zero mq的作者就曾經說過,異常機制無法做到錯誤處理和引發錯誤的地方的耦合,我想他們都理解錯了異常機制出現的目的
2.異常與返回值判斷錯誤的區別(我自己的理解)
作為一種容錯機制,異常並未想要取代返回值檢查錯誤的改進,其中一個最顯著的區別就是返回值即使你不是處理,並不會導致程序的崩潰,而異常會,即返回值的默認語義就是:
我這個函數會發生錯誤
即使我發生錯誤,你可以無視我的錯誤
而異常機制的語義就是:
如果我發生錯誤,你必須處理我這個錯誤,否則程序就會崩潰
異常機制強制你去處理一些事情,你必須明確的知道我這個函數中,我發生錯誤,並且我處理了他,
而返回值則沒有這種強制性
所以我建議大家在編寫c++代碼的時候,要考慮下,如果我這個函數出現了錯誤,我是否允許一些程序繼續往下執行??再來決定是否使用異常還是返回值,以上僅僅是我的個人建議,而不是bjarne stroustrup的建議
3.為什麼c++不讓用戶知道函數會引發什麼異常
既然異常機制的語義是如果我發生錯誤,你必須處理我這個錯誤,但是在函數的聲明上,我們很難看出來這個函數是否需要捕獲異常,因此,最早c++的設計團隊有這樣的想法,就是在函數的聲明加上異常拋出的語義
void function(int args) throw e1,e2
這樣用戶在看到這個函數聲明的時候就知道這個函數會拋出什麼異常,其實我覺得這樣挺方便的,現在我無法知道內部程序會拋什麼異常一直是我所困擾的,如果這樣,我們就可以在編譯的時候知道我們的程序有多少未捕獲的異常
假設c++真的實現了異常的靜態檢查,我們在編譯的時候就可以直接檢測到代碼中的錯誤,會發生什麼問題??
假設我們有三個函數,其中存在不同的三個模塊,
分別為function0調用function1,function1調用function2,此時function2拋出異常e1,需要function0處理,function1和function0的模塊為了不編譯錯誤,function1必須捕獲函數並且把他重新throw,而function0必須處理這個異常
後面,function2增加了異常拋出e2,此時我們為了通過編譯,我們需要重新修改function1的代碼,並且重新編譯function1,這個修改也會影響到function0,於是我們也必須去修改function0,這樣,將會導致多余的代碼修改以及重編譯,這個不是c++的設計團隊所希望看到的
所以後來bjarne stroustrup認為這樣的靜態檢查並不是c++所希望的,他更認為這種檢查將由另外一種工具來檢查更好
我在想這種語法作為一種提示語法也並非不可,只是作為提示用,編譯器不會對此進行保證,不過回頭想想,這跟注釋有什麼區別??
4:異常的核心就是資源管理
在異常裡面還有一個比較頭疼的就是資源管理,bjarne stroustrup也明確指出異常的設計核心實際上是資源的管理,原因在於如果一個程序打開了一個文件,程序在運行過程中拋出了異常,而文件關閉的代碼則在程序拋出異常代碼的後面,那麼則文件無法正常關閉
於是這個時候,RAII就誕生了,我看到這裡也感到有點詫異,RAII居然是因為異常誕生的
於是,不管我們是否進行文件關閉操作,只要該函數析構掉,我們的文件總能正常關閉
bjarne stroustrup也提出異常處理給構造函數提供了一種報告出錯的直接方法,如果沒有異常函數,那麼我們將只能迂回地去檢測這個函數是否構造完成,與此相反,scotter meyers還是hutter(忘記是誰了,反正是一位c++大神)建議,就是不要在構造函數中拋出異常,因為如果在構造函數裡面拋出異常,那個申請的資源可能會洩漏,比如
x = new X()
如果在構造x的過程中拋出異常,那麼你就無法刪除new X申請的資源,這個也成了很多人嘲笑c++異常的例子
於是乎,很多程序轉而另外定義一個init來供外部調用,這個也是我經常在網上看到的建議,於是你經常能看到如下的代碼:
x = new X()
try
{
x->init();
}
catch(...)
{
}
但是bjarne stroustrup對對這種做法不贊成(http://www.cise.ufl.edu/~manuel/stroustrup/ex.pdf),
Having a separate init() function is an opportunity to
– Forget to call init()
– Call init() twice
– Forget to test that init() succeeded
– Forget that init() might throw an exception
– Use an object before calling init()
對此,scotter meyers在他的more effective c++的item 10有給出建議,總的來說,還是要遵守不要在構造函數裡面對外拋出異常,而是在構造函數內處理異常,並且在構造函數內利用RAII來達到自動管理資源的目的,當然後一條不是必須的,只要你能保證你能夠正常釋放資源即可,利用RAII只是為了方便而已
5.為什麼沒有辦法從發生異常的地方繼續執行
我想這個是很多人一直想要的功能,當時這個問題c++討論組也討論了很久,最後從以往的設計角度上來看,這種所謂的喚醒機制都是不靠譜的,因此沒有加入,有一個os最早就是朝著這個方向設計的,後來幾乎把這部分喚醒功能全部都去掉了,我想估計c++委員會也是基於這樣的考慮才不加入這個功能
上面的就差不多是c++異常機制設計的一些思路了,當然這裡面參雜了我的一些看法,其實從這裡面來看,很多人不喜歡c++是因為他不了解c++一些特性出現的原因,可能他不知道這個在c++裡面實際上是錯誤的用法,只是c++沒去禁止你使用而已
最近看<<C++的歷史和演化>>感覺還是挺有意思的,了解c++的歷史就知道我們在過去有多少錯誤的c++使用例子了
同時推薦下bjarne stroustrup的文章 Standard-Library Exception Safety
http://www.cise.ufl.edu/~manuel/stroustrup/ex.pdf