2008-10-13 39 views

回答

27

可能是一個明顯的觀點 - 開發人員可以忽略(或不知道)你的回報狀態,並繼續幸福地意識到某件事失敗。

需要以某種方式承認異常 - 如果不主動提供適當的位置,不能默默忽略它。

+2

您得到了以下兩點之一:您可以輕鬆將用於處理錯誤的代碼移動到非本地點(呼叫更改的較高位置),並知道如何在給定更廣泛的上下文的情況下處理問題。 – 2008-12-10 05:12:33

10

異常處理很有用,因爲它可以很容易地將錯誤處理代碼與編寫用於處理程序功能的代碼分開。這使得閱讀和編寫代碼變得更容易。

7
  • 返回時發生錯誤情況預計在某些情況下
  • 拋出一個異常時發生錯誤情況預計在任何情況下
在前者的情況下

的錯誤代碼函數的調用者必須檢查錯誤代碼是否有預期的失敗;在後一種情況下,例外情況可由任何呼叫者在棧上(或默認處理程序)處理(如適用)

17

好處是,您不必在每次潛在呼叫失敗後檢查錯誤代碼。爲了讓這個工作起來,你需要將它與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包裝的保護)。但是你最終不太理會事情會出錯的地方。

+0

您也可以結合RAII和常規錯誤代碼。 return語句在某種程度上等同於throw,因爲它們都會調用範圍內的堆棧對象的dtors。所以不需要轉到。 – QBziZ 2008-10-13 09:46:52

+0

好點。在我的腦海裏,錯誤代碼總是用於C,所以我的大腦並沒有飛躍。 – Eclipse 2008-10-13 15:30:30

2

Here's EAFP的一個很好的解釋(「容易問寬恕比權限。」),我認爲這適用於這裏,即使它是維基百科的Python頁面。使用例外會導致更自然的編碼風格,海事組織 - 以及其他許多人的看法。

8

除了提到的其他內容,您不能從構造函數返回錯誤代碼。也可以使用析構函數,但是也應該避免從析構函數中拋出異常。

0

正如@馬丁指出拋出異常迫使程序員處理錯誤。例如,不檢查返回代碼是C程序中最大的安全漏洞來源之一。例外情況確保您處理錯誤(希望),併爲您的程序提供某種恢復路徑。如果您選擇忽略異常而不是引入安全漏洞,那麼您的程序將崩潰。

20

例外的優勢有兩個方面:

  • 它們不能被忽略。你必須在某個級別處理它們,否則他們會終止你的程序。有了錯誤代碼,您必須明確檢查它們,否則它們會丟失。

  • 它們可以被忽略。如果一個錯誤無法在一個層次上處理,它會自動在其可能的位置上升到下一個層次。錯誤代碼必須顯式傳遞,直到達到可以處理的級別。

+0

這應該是IMO的最佳答案! – Gob00st 2012-01-10 15:09:53

+0

可否請您詳細說明第二點(也許有代碼示例),我不太瞭解它 - 謝謝。 – Xsmael 2017-03-23 18:09:25

+1

@Xsmael假設FuncA()調用FuncB(),它調用調用FuncD()的FuncC()。 FuncD()可以引發異常,如果FuncB或FuncC中沒有捕獲,它可以在FuncA()中捕獲。使用錯誤代碼,FuncC必須接受來自FuncD的代碼,並將其返回給FuncB。 – 2017-03-24 07:52:57

1

有時你真的必須使用異常來標記異常情況。例如,如果在構造函數中出現錯誤,並且發現通知調用者這是有意義的,那麼您別無選擇,只能拋出異常。

又如:有時候沒有價值你的函數可以返回來表示一個錯誤;函數可能返回的任何值都表示成功。

int divide(int a, int b) 
{ 
    if(b == 0) 
     // then what? no integer can be used for an error flag! 
    else 
     return a/b; 
} 
2

當我過去教C++時,我們的標準解釋是,他們允許您避免糾結晴天和雨天的情況。換句話說,你可以編寫一個函數,就好像一切都可以正常工作,並在最後捕獲異常。

沒有異常,你就一定得從每個調用的返回值,並確保它仍然是合法的。

當然,一個相關的好處是你不會「浪費」你的異常返回值(因此允許方法應該是無效的),並且還可以從構造函數和析構函數中返回錯誤。

4

我寫這個博客條目(Exceptions make for Elegant Code),隨後發表在Overload。我實際上是爲了迴應Joel在StackOverflow播客中所說的話而編寫的!

無論如何,我堅信,例外是最好錯誤在大多數情況下代碼。我發現使用返回錯誤代碼的函數真的很痛苦:每次調用後都必須檢查錯誤代碼,這可能會中斷調用代碼的流程。這也意味着你不能使用重載操作符,因爲沒有辦法指示錯誤。

檢查錯誤代碼的痛苦意味着人們經常忽視這樣做,因此使它們完全沒有意義:至少你必須明確地忽略與catch聲明的異常。

使用在C++析構函數和處置者在.NET,以確保資源在例外也可以極大地簡化代碼的存在正確釋放。爲了獲得與錯誤代碼相同的保護級別,您可能需要大量的if語句,大量重複的清理代碼或goto調用在函數結束時的公用清理塊。這些選項都不令人愉快。

1

事實上,你必須承認異常是正確的,但這也可以使用錯誤結構來實現。 您可以創建一個基本錯誤類,用於檢查其dtor中是否調用了某個方法(例如IsOk)。如果沒有,你可以登錄一些東西,然後退出,或拋出異常,或提出斷言等...

只要調用錯誤對象上的IsOk而不作出反應,那麼將等同於寫入catch (...){} 這兩個陳述都會顯示出同樣缺乏程序員的善意。

將錯誤代碼傳輸到正確的級別是一個更大的問題。基本上,爲了傳播的原因,幾乎所有的方法都會返回一個錯誤代碼。 但是,然後再次,一個函數或方法應該總是註釋它可以生成的例外。所以基本上你必須有同樣的問題,沒有一個接口來支持它。

2

Google's C++ Style Guide對C++代碼中異常使用的優缺點有很好的深入分析。這也表明你應該提出一些更大的問題;即我是否打算將我的代碼分發給其他人(可能難以集成啓用異常的代碼庫)?

相關問題