C++中編寫強異常安全的代碼真的有必要麼?

眾所周知C++里想寫出所謂的強異常安全(也就是既不會導致泄露 還能完美的恢復到調用前的狀態)的代碼非常困難

而且 我個人贊成永遠不要用異常來控制程序流程

所以對我來說異常的作用 只是在發生異常的時候 知道什麼地方出錯了

並且在最終發布版本會禁止異常

而且我永遠不會繼續讓程序執行下去

因為如果讓他繼續執行下去 等於違背了不要用異常來控制程序流程這條

你等於把異常當作流程式控制制 當發生的時候

居然還期盼著它能完美解決問題 然後程序繼續執行

就像如果一個數除以0

結果拋出一個異常 就應該中斷程序 因為這必然是程序邏輯出現了錯誤

而不是默默的當作什麼都沒發生 讓異常來進行所謂的處理 吞沒這個問題

繼續執行後續程序 直到遇到一個未處理的異常 導致崩潰

然後開始無盡的debug

我認為異常不應該嘗試去解決任何問題

如果能解決的問題 根本就不應該是一個異常

而是這個函數的正常功能

所以我覺得 所有異常都等於ERROR

出現就應該中斷程序 報告錯誤

而不是偷偷的處理 掩埋錯誤

於是我就想 所謂的異常安全代碼 真的有必要麼

如果按我對異常的理解

完全沒有必要

我不需要恢復現場 也不需要關心是否有東西泄露

因為程序必然被終止

我要做的只是應該去修改代碼

完全正確的程序 我認為 不應該出現任何異常


我只覺得題主為了偷懶,實在是殫精竭慮啊。

按這個邏輯……

如果某個人的人生中出現了一個別人引起的問題(如他女友劈腿導致失戀),是不是他就可以去死了?


因噎廢食。

首先我需要強調一句:【應該使用異常】跟【異常是用來catch的】是兩個毫不相干的命題。我從來不覺得拋了異常就一定要catch。這也是為什麼異常有那麼多類型,你可以自由決定什麼樣的異常拿來做控制流,什麼樣的異常拿來打dump崩潰。如果你覺得不應該有控制流異常也沒關係,那你就都打dump好了。

把所有STL能代替C語言的那部分內容,都用STL,不要用C語言的feature來做,寫異常安全的代碼是很簡單的,根本就不需要刻意去注意什麼。題主應該更加深入的學習。

至於說你把異常幹掉了,重新撿回error code這種事情,以前早就評論過:如何設計一門語言(三)——什麼是坑(面向對象和異常處理)

對於已經熟悉了如何「把所有STL能代替C語言的那部分內容,都用STL」的人來說,在開發一個新的程序的時候,不用異常只是徒增痛苦。

然後說道題主的這段話:

於是我就想 所謂的異常安全代碼 真的有必要麼

如果按我對異常的理解

完全沒有必要

我不需要恢復現場 也不需要關心是否有東西泄露

因為程序必然被終止

我要做的只是應該去修改代碼

完全正確的程序 我認為 不應該出現任何異常

如果你的程序在被 政府 / 工業級最終用戶 使用的時候出了翔,你也不打算去修,也不打算賠錢,也不打算派人去跪著處理,公司被告倒了也沒關係的話,那你怎麼掛,怎麼不打dump,怎麼後台不維護crash系統,怎麼忽略Windows的Watson,都沒關係。這跟你用不用異常沒有任何關係。

玩具程序不在評論範圍內。


輪子說得很好了,閑來補充幾點,盡量大白話一些。

首先,程序的本質,就是處理邏輯,包括各種I/O、順序、分支、迭代、遞歸、複雜演算法等等。

所有的程序都是這樣的,本來所有的狀態都應當被適當處理,不應當有「異常」出現。

但是事情並不像我們想像的那麼簡單。

世界是複雜的,它並不會完全按照我們的設想去給程序各種輸入和資源,包括人的輸入。

所以在正經的商業程序中,甚至有高達8-9成的代碼是用來處理各種非期望的輸入和資源的。

這就是所謂「異常處理」,否則程序動不動崩潰,大家誰也別想好好玩。

不就是個IF...ELSE語法么,好好寫就好了。

但是後來程序員們寫這玩意寫得精神崩潰了,太TMD多了,太累了。

而且寫這玩意不僅累,還很容易把正常邏輯弄得很混亂,程序的設計清晰性、可讀性、可維護性都糟糕透頂,這就不僅僅是純體力活的損失了。

於是程序員們就在編程語言中引入了「異常」的標準概念。

為啥呢?

因為程序員們經過統計,發現異常的種類和處理方法,在大多數情況下就是那麼一些,並不是格外千奇百怪的,一般都是「輸入不對啊、需要的資源沒有或者被佔用了啊、發生了一個不影響主邏輯的小錯誤啊、發生了一個意料之外的事件啊……」。

處理方法呢,也無外乎「忽略它、提示錯誤、重新來一遍流程、等待資源、記個日誌方便程序員或使用者檢查處理、我操這個錯誤太嚴重了程序必須終止……」 。

看來可以歸一化統一處理呢,不用到處寫case或者break了。

引入異常以後,設計就更清晰明了了。

現在我們可以集中精力設計主業務流程,對各種奇怪的小事情直接拋出異常就好了。

然後由集中設計的異常處理機制捕捉這些異常,進行對應的處理。

不用到處寫case,多好。

回到題目,「強異常安全的代碼編寫」,其實才算是正確地使用異常機制,特別是商用程序。

因為這本來就是商用程序應當做的事情,跟有沒有異常機制本身無關。

不能因為真實世界跟你想得稍有不同就大發雷霆系統崩潰啊!

too sample,too navie。


異常不一定是因為你的程序有邏輯問題,它可能來自:用戶不靠譜的輸入、網路間歇性故障、各種軟硬體問題,比如資料庫跪了、埠被佔了、許可權被改了等等。也就是說,即使你的代碼十分完善,異常仍是完全可能發生的。異常機制提供了讓你知道當時發生了什麼的機會,如果沒有log,你很可能無法知道彼時出了什麼問題(重現不出來)。

另一種場景,假如你要處理一堆文件,正常需要兩天才能跑完,但這堆文件里有個文件格式不對。當處理這個文件時,你希望你的程序是把異常log打出來,然後繼續跑後面的文件,還是直接整個崩掉?當然你也可以寫無數的if-else來處理現實中可能出現的各種無厘頭情況,但毫無疑問是try-catch會簡潔。


LZ的「不應該掩蓋問題」的想法是非常正確的,但你把板子打在異常身上一點道理也沒有。

就像如果一個數除以0

結果拋出一個異常 就應該中斷程序 因為這必然是程序邏輯出現了錯誤

而不是默默的當作什麼都沒發生 讓異常來進行所謂的處理 吞沒這個問題

繼續執行後續程序 直到遇到一個未處理的異常 導致崩潰

然後開始無盡的debug

出現致命錯誤,該崩就崩,不該藏著掖著,再正確不過了。但為什麼LZ會認為異常的用途是掩蓋問題異常的設計哲學反而是防止掩蓋問題,因為異常只要拋出就必須處理,否則程序就崩潰。如果異常被用來掩蓋問題,那是用的人有問題,而不是異常設計得有問題。

我認為異常不應該嘗試去解決任何問題

如果能解決的問題 根本就不應該是一個異常

而是這個函數的正常功能

異常是用來表達和處理任何不正常的情況的,包括明顯能解決的,和不能解決的。如果你覺得能解決的問題不該用「異常」來表達的話,那看上去更像是你被字眼摳住了沒有詳細了解「異常」這個名字背後的概念。換個Java里的名字Throwable(拋出物),你用不?

你覺得不應該掩蓋問題,需要做的是不接相應的異常,而不是禁用異常

剩下的無非是用異常處理問題還是用error code處理問題哪個更優了。這早已有定論。使用異常需要你認真寫異常安全的代碼作為代價,但異常的優越性遠高於error code。

==============================================

補充:

強異常安全≠異常安全

強異常安全≠異常安全

強異常安全≠異常安全

重要的話說三遍,請區分這兩個概念。

強異常安全的意思是原子化,函數要麼完全完成功能,要麼異常退出,所有修改卷回。涉及IO操作和系統調用時,強異常安全基本是實現不了的,強異常安全也不是人們要求的標配,有更好,沒有也可以

異常安全指的是,如果發生異常,你的函數在離開之前,能正確清理資源,不要泄露,不要引發其他異常。達到這個標準就能使用異常

能保證強異常安全,一定能保證異常安全。反之不行。


其實題主說了一大堆,核心的問題在於把代碼中的bug等價於異常……

前者是設計失誤,後者程序業務邏輯的一部分。

出現異常並不意味著程序本身出了問題,而是程序外部的輸入無法讓正常業務繼續執行,這時應該執行的是異常業務,而異常就是這兩種業務轉接的橋樑——它提供一種機制,使得程序員在遵守一定規則之下,這個異常業務能夠正確完成。


The Unix-Haters Handbook

美國長者Ken Thompson自己設計過一輛汽車。

它和其他車不同,沒有速度計、汽油計,也沒有那些愚蠢的指示燈討司機的厭。

如果司機犯了什麼錯誤,儀錶盤上就會出現一個大大的「哈?」。

「有經驗的老司機」(Thompson說)「應該知道哪兒搞錯了。」


我們的伺服器鎖在保密室里,服務端程序出個異常就退出的話,我得整天盯著服務端會不會出現異常後退出了,然後開保密室去重啟程序嗎?管鑰匙的都得打死我。


異常安全分三種,nothrow,strong和basic,各有各的用處,並非是簡單的放寬。在一個常規的實際項目中,nothrow和strong是必備,basic也基本少不了。何來都是強保證一說?

對於現代c++而言,不是異常安全的代碼都是玩具或渣渣。這一點是沒有疑問的。


就像如果一個數除以0

結果拋出一個異常 就應該中斷程序 因為這必然是程序邏輯出現了錯誤

正好最近我同學做的一很水的課設是計算器=_=


並不寫包含異常代碼,堅持Google Code Style/LLVM Code Style的無異常原則。經手項目自帶-fno-exceptions flag。

當然這樣也用不了boost,然而並沒有什麼缺失。無異常代碼以後,業務邏輯更清晰,維護也更簡單了。

這樣自然也算是「exception-safe」。


樓主不太理解異常是用來幹嘛的。比如除零的情況,根本不是一個異常,而是導致一個除零中斷,正常來說程序直接就core掉了。

異常用來處理那些業務層不用太關注的錯誤情況,比如我連接的後台資料庫連不上了。如果要求每一次訪問都要通過返回值確認成功,那麼你的代碼量會翻一倍,更悲劇的事,這種繁瑣的事,總是有人會忘掉做,這樣很可能第一步失敗接著做第二步,帶來的問題更大。當然,你可以頂著手下團隊幾十人埋汰的壓力下,做一個掃描工具來檢測類似情況,反正你是老闆嘛,我就喜歡代碼長,看著帶勁。

當然,另一種場景是,老闆你說了,資料庫都連不上,服務掛掉好吧。好吧,按照你意見的新版本上線了,然後運維同學報告服務每小時重啟一次,分析後發現,資料庫不是連不上,只是有時候連不上,忙時就不太好了,當然這樣的服務有個好處,肯定不會給用戶返回什麼server busy的錯,很有日本皇軍做事不到位就刨腹自殺的風采。

最後說一下怎麼實現所謂的異常安全代碼,一句話,資源都對象化,析構時自然做了。


我覺得異常比error code先進的地方在於他可以更方便的跨越函數的層級去處理非正常情況,還可以攜帶更多運行時的信息。


曾經嘗試過用 C++ 寫一個異常安全的模塊, 發現實在是難寫, 為什麼?

異常安全, 是發生任何異常都不能讓數據結構, 狀態信息出於不一致的狀態 (不僅僅是內存泄露, 那個簡單)

模塊函數裡面, 調用任何一個函數, 甚至使用操作符, 都要小心這裡面會不會拋出異常, 然後要進行處理, catch 處理代碼裡面, 也還有可能會碰到異常, 代碼怎麼寫怎麼亂.

到後來, 就乾脆用 c 實現這部分了, 簡單直接.

go 來做這樣的任務, 也是簡單.


寫出strong異常安全代碼的水平是屬於精通級吧

目前遇到的人大多數不理解什麼是strong級異常安全。

我也是,幾位元組的內存都申請不了還要恢復狀態真的是...屁眼撐的那麼大就為了生的蛋多賣一毛錢的感覺……


異常處理對於不同場景,不同用戶群要求是不一樣的。對於一般的軟體,oops就oops,但是如果你的軟體用於銀行核心系統,電信,電力等行業,系統問題導致業務停了,就等著賠錢吧。


C++寫得不多,但是可以想像的是如果程序連接伺服器沒有網路,然後拋個異常難道也要退出嗎?


推薦閱讀:

能不能對QVariant進行引用/指針讀寫數據?
eclipse為什麼不能做的好用一點?好看一點?
土豪程序員的設備都有啥?
大家是怎麼念 null 的?
為什麼 Eclipse 如此流行?

TAG:編程 | C | 異常處理 |