我已經編程了很長一段時間C和C++,到目前爲止,我從來沒有使用異常和嘗試/捕獲。使用它的好處是什麼,而不是隻有函數返回錯誤代碼?在C++中使用異常和try/catch而不是僅返回錯誤代碼有什麼好處?
回答
可能是一個明顯的觀點 - 開發人員可以忽略(或不知道)你的回報狀態,並繼續幸福地意識到某件事失敗。
需要以某種方式承認異常 - 如果不主動提供適當的位置,不能默默忽略它。
您得到了以下兩點之一:您可以輕鬆將用於處理錯誤的代碼移動到非本地點(呼叫更改的較高位置),並知道如何在給定更廣泛的上下文的情況下處理問題。 – 2008-12-10 05:12:33
異常處理很有用,因爲它可以很容易地將錯誤處理代碼與編寫用於處理程序功能的代碼分開。這使得閱讀和編寫代碼變得更容易。
- 返回時發生錯誤情況預計在某些情況下
- 拋出一個異常時發生錯誤情況不預計在任何情況下
的錯誤代碼函數的調用者必須檢查錯誤代碼是否有預期的失敗;在後一種情況下,例外情況可由任何呼叫者在棧上(或默認處理程序)處理(如適用)
好處是,您不必在每次潛在呼叫失敗後檢查錯誤代碼。爲了讓這個工作起來,你需要將它與RAII類結合起來,以便在堆棧展開時自動清理所有內容。
隨着錯誤消息:
int DoSomeThings()
{
int error = 0;
HandleA hA;
error = CreateAObject(&ha);
if (error)
goto cleanUpFailedA;
HandleB hB;
error = CreateBObjectWithA(hA, &hB);
if (error)
goto cleanUpFailedB;
HandleC hC;
error = CreateCObjectWithA(hB, &hC);
if (error)
goto cleanUpFailedC;
...
cleanUpFailedC:
DeleteCObject(hC);
cleanUpFailedB:
DeleteBObject(hB);
cleanUpFailedA:
DeleteAObject(hA);
return error;
}
例外與RAII
void DoSomeThings()
{
RAIIHandleA hA = CreateAObject();
RAIIHandleB hB = CreateBObjectWithA(hA);
RAIIHandleC hC = CreateCObjectWithB(hB);
...
}
struct RAIIHandleA
{
HandleA Handle;
RAIIHandleA(HandleA handle) : Handle(handle) {}
~RAIIHandleA() { DeleteAObject(Handle); }
}
...
在乍看之下,RAII /例外版本似乎更長,直到你意識到,僅需要編寫清理代碼一次(並且有方法可以簡化)。但DoSomeThings的第二個版本更加清晰和可維護。
不要試圖在C++中使用異常而不使用RAII語言,因爲會泄漏資源和內存。所有的清理都需要在堆棧分配對象的析構函數中完成。
我意識到還有其他的方式來做錯誤代碼處理,但他們都看起來有點相同。如果你放棄goto,你最終會重複清理代碼。
錯誤代碼的一個要點是,它們明確了事情可能發生故障的位置以及它們如何失敗。在上面的代碼中,你假設事情不會失敗(但是如果他們這樣做,你會受到RAII包裝的保護)。但是你最終不太理會事情會出錯的地方。
Here's EAFP的一個很好的解釋(「容易問寬恕比權限。」),我認爲這適用於這裏,即使它是維基百科的Python頁面。使用例外會導致更自然的編碼風格,海事組織 - 以及其他許多人的看法。
除了提到的其他內容,您不能從構造函數返回錯誤代碼。也可以使用析構函數,但是也應該避免從析構函數中拋出異常。
正如@馬丁指出拋出異常迫使程序員處理錯誤。例如,不檢查返回代碼是C程序中最大的安全漏洞來源之一。例外情況確保您處理錯誤(希望),併爲您的程序提供某種恢復路徑。如果您選擇忽略異常而不是引入安全漏洞,那麼您的程序將崩潰。
例外的優勢有兩個方面:
它們不能被忽略。你必須在某個級別處理它們,否則他們會終止你的程序。有了錯誤代碼,您必須明確檢查它們,否則它們會丟失。
它們可以被忽略。如果一個錯誤無法在一個層次上處理,它會自動在其可能的位置上升到下一個層次。錯誤代碼必須顯式傳遞,直到達到可以處理的級別。
有時你真的必須使用異常來標記異常情況。例如,如果在構造函數中出現錯誤,並且發現通知調用者這是有意義的,那麼您別無選擇,只能拋出異常。
又如:有時候沒有價值你的函數可以返回來表示一個錯誤;函數可能返回的任何值都表示成功。
int divide(int a, int b)
{
if(b == 0)
// then what? no integer can be used for an error flag!
else
return a/b;
}
當我過去教C++時,我們的標準解釋是,他們允許您避免糾結晴天和雨天的情況。換句話說,你可以編寫一個函數,就好像一切都可以正常工作,並在最後捕獲異常。
沒有異常,你就一定得從每個調用的返回值,並確保它仍然是合法的。
當然,一個相關的好處是你不會「浪費」你的異常返回值(因此允許方法應該是無效的),並且還可以從構造函數和析構函數中返回錯誤。
我寫這個博客條目(Exceptions make for Elegant Code),隨後發表在Overload。我實際上是爲了迴應Joel在StackOverflow播客中所說的話而編寫的!
無論如何,我堅信,例外是最好錯誤在大多數情況下代碼。我發現使用返回錯誤代碼的函數真的很痛苦:每次調用後都必須檢查錯誤代碼,這可能會中斷調用代碼的流程。這也意味着你不能使用重載操作符,因爲沒有辦法指示錯誤。
檢查錯誤代碼的痛苦意味着人們經常忽視這樣做,因此使它們完全沒有意義:至少你必須明確地忽略與catch
聲明的異常。
使用在C++析構函數和處置者在.NET,以確保資源在例外也可以極大地簡化代碼的存在正確釋放。爲了獲得與錯誤代碼相同的保護級別,您可能需要大量的if
語句,大量重複的清理代碼或goto
調用在函數結束時的公用清理塊。這些選項都不令人愉快。
事實上,你必須承認異常是正確的,但這也可以使用錯誤結構來實現。 您可以創建一個基本錯誤類,用於檢查其dtor中是否調用了某個方法(例如IsOk)。如果沒有,你可以登錄一些東西,然後退出,或拋出異常,或提出斷言等...
只要調用錯誤對象上的IsOk而不作出反應,那麼將等同於寫入catch (...){} 這兩個陳述都會顯示出同樣缺乏程序員的善意。
將錯誤代碼傳輸到正確的級別是一個更大的問題。基本上,爲了傳播的原因,幾乎所有的方法都會返回一個錯誤代碼。 但是,然後再次,一個函數或方法應該總是註釋它可以生成的例外。所以基本上你必須有同樣的問題,沒有一個接口來支持它。
Google's C++ Style Guide對C++代碼中異常使用的優缺點有很好的深入分析。這也表明你應該提出一些更大的問題;即我是否打算將我的代碼分發給其他人(可能難以集成啓用異常的代碼庫)?
- 1. 爲什麼拋出異常而不是返回錯誤代碼更好?
- 2. 爲什麼fopen()或open()使用errno而不是僅僅返回錯誤代碼?
- 3. 使用put和delete有什麼好處,而不僅僅是獲取和發佈
- 4. 什麼時候應該拋出異常而不是在PHP中返回錯誤?
- 5. 返回使用dict構造的散列而不是僅使用花括號語法是否有什麼好處?
- 6. 返回代碼錯誤和錯誤有什麼區別
- 7. 使用ORDBMS代替RDBMS有什麼好處,而不是JPA
- 8. 爲什麼不使用錯誤數組而不是異常處理?
- 9. 從函數返回值而不是返回void有什麼好處嗎?
- 10. 使用calloc()而不是malloc()和memset()有什麼好處嗎?
- 11. 在異步回調鏈中使用try catch塊返回異常有什麼好處?
- 12. 在「驗證」類或返回狀態代碼中使用異常是否更好?
- 13. 在C++中使用錯誤代碼的更好方法是什麼?
- 14. C代碼中的錯誤是什麼?
- 15. 使用Doctrine使用DQL而不是SQL有什麼好處?
- 16. 爲什麼不是以下解析代碼返回錯誤?
- 17. 爲什麼HttpWebRequest會拋出異常而不是返回HttpStatusCode.NotFound?
- 18. JavaScript返回「結果」而不是錯誤或異常
- 19. 使用<=>而不僅僅是排序和反轉有沒有好處?
- 20. 未處理的異常返回垃圾字符,而不是錯誤
- 21. 爲什麼它更好(返回IList而不是返回列表)?
- 22. web.py有什麼問題? SystemError:錯誤無異常返回設置
- 23. 爲什麼我的AJAX錯誤沒有返回Webmethod異常?
- 24. 錯誤處理範例:混合異常和錯誤代碼
- 25. 爲什麼當我使用|時這個C代碼段錯誤而不是||?
- 26. 爲什麼我的代碼總是返回「錯誤的用戶名和密碼」?
- 27. IEnumerable不是,但在迭代(C#)返回空引用異常
- 28. Java中的異常和錯誤代碼
- 29. 拋出異常(C#)返回錯誤號
- 30. Perl的異常而不是返回值
查看James Curran的答案爲__> 2 <__的主要原因。 – 2008-12-10 05:13:27