C 程式設計師應了解的那些事(98)關於C 異常

2021-10-17 08:50:37 字數 3551 閱讀 6857

本文是作者翻譯過c++之父bjarne stroustrup的技術文章c++核心準則中有關c++中異常的文章之後的總結:

異常處理機制希望解決的問題:

為了使用錯誤處理系統化,健壯和不繁瑣。例如下面的**:

void f2(int i) // clumsy and error-prone: explicit release

; }

// ...

}

**需要針對每種錯誤進行處理,更為複雜的是當程式的規模達到一定程度之後,在各個模組之間和呼叫的各個層級之間傳遞錯誤資訊也會變成乙個巨大的負擔。異常就是為了解決這個問題而出現的。

一般情況下會認為異常意味著重大的例外事件和錯誤。例如下面的情況:

通過丟擲異常來向呼叫者表明函式無法執行指定的任務。

不應該使用異常的情況

迴圈的正常終止,處理的正常結束都是正常和期待的動作,不應該被視為異常。這種做法可以保證錯誤處理和「普通的**」分離。c++編譯器會很罕見的以異常處理為前提進行**優化。不要使用將丟擲異常作為從函式中返回結果的另一種方式使用。

使用異常時應防止資源洩露

資源洩露通常都是不可接受的。如果只是簡單的去掉原有的錯誤處理**並增加異常丟擲和處理**,通常會發生資源洩露。例如下面的**:

void leak(int x)   // don't: may leak

; if (x < 0) throw get_me_out_of_here{}; // may leak *p

// ...

delete p; // we may never get here

}

手動釋放資源雖然不是完全做不到,但是工作量巨大且容易引發錯誤。

void f2(int i)   // clumsy and error-prone: explicit release

; }

// ...

}

這樣的**過於冗長,甚至比不用異常的**更加冗長。在更大規模的,存在更多的丟擲異常的可能性的**中,顯式釋放資源會更加繁複和易錯。解決這個問題的方法是raii(「資源請求即初始化」),它是防止洩露最簡單,更加系統化的方式。

void f3(int i)   // ok: resource management done by a handle (but see below)

; // ...

}

另外乙個解決方案(通常更好)是用區域性變數來避免使用指標!

void no_leak_simplified(int x)

定義和使用自己的異常型別

使用使用者定義型別的好處是避免和其他人的異常發生衝突。這種問題在**規模變大之後會在不知不覺**現。繼承自exception的標準庫類應該只用於基類或只要求「通常」處理的異常。和內建型別相似,對它們的使用也有可能和其他人的使用發生衝突。

使用常量引用形式捕捉繼承體系中的異常

為了避免資料截斷。大多數處理程式不會改變異常的內容,因此通常我們同時推薦使用常量形式。

正確排列catch子句

catch子句按照它們表示的次序執行,乙個子句處理之後,其他子句不再執行。

void f()

catch (base& b)

catch (derived& d)

catch (...)

catch (std::exception& e)

}

如果deriveds是base的派生類,捕捉派生類的處理永遠不會執行。捕捉所有異常的處理會導致捕捉std::exception的處理程式永遠不會執行。

重新丟擲異常

重新丟擲已經捕獲的異常時一定要使用throw;而不是throw e; 使用後者會丟擲乙個e的新拷貝(靜態型別std::exception的截斷結果)而不是重新丟擲原始異常。

關於noexcept

為了讓錯誤處理更系統化,健壯和高效可以為函式定義noexcept。因為某段**由不會丟擲異常的操作構成,所以我們知道某函式不會丟擲異常。通過將函式定義為noexcept,我向編譯器和**的讀者傳遞了可以讓它們更容易理解和維護的資訊。很多標準庫函式被定義為noexcept,包含所有從c標準庫繼承的標準庫函式。

但應該注意的是,一旦定義了noexcept,c++編譯器就會放棄為函式生成接受、**異常的處理。如果實際發生了異常,結果是毀滅性的。一定要慎重定義noexcept。

析構函式,記憶體釋放和swap操作永遠不能失敗!

如果析構函式、swap操作或者記憶體釋放失敗了,我們不知道如何編寫可信賴的處理程式;標準庫假設析構函式,記憶體釋放函式(例如delete運算子),swap都不會丟擲異常。如果它們異常,標準庫的前提條件就被破壞了。

不要試圖在所有函式中捕捉所有異常

在乙個無法提供有意義的恢復操作的函式中捕捉錯誤會導致**複雜化和冗餘。讓異常向外傳播直到到達乙個可以處理它的函式。讓raii處理呼叫路徑上的清理動作。

try/catch結構冗長,非平凡的用法容易出錯。try/catch可以看作是非系統化和低層次資源管理或錯誤處理的訊號。

最小限度顯式使用try/catch。

無法使用異常的情況

有些系統,例如硬實時系統要求保證乙個動作在開始執行之前就能確定其執行時間小於某個固定值(通常很小)。這樣的系統只有在存在某種可以準確**系統從丟擲異常過程中恢復的最大時間的工具時才可以使用異常。如果沒有適當的時間評價工具,異常處理機制很難滿足這個要求。這樣的系統(例如飛行控制系統)通常也會禁止使用動態(堆)記憶體。

不要使用拋異常宣告

拋異常宣告本來的目的是明確表明某個函式可能丟擲的異常。

int use(int arg) throw(x, y)

但是異常宣告讓錯誤處理更脆弱,並強制產生執行時成本,已經從c++標準中被移除了。在不會丟擲任何異常時,使用noexcept或者和它等價的throw()是才更加正確的做法。

關於異常代價和效能

很多關於異常的大量恐懼都是被誤導的。當在沒有被指標或複雜的控制結構搞亂的**環境中使用異常時,異常處理幾乎總是可以接受的(無論是時間還是空間維度),幾乎總是可以帶來更好的**。

在譴責異常或抱怨異常的成本過高之前,考慮使用錯誤**時的成本和複雜度。如果你擔心效能,進行測量(而不是無根據的懷疑,譯者注)。

參考資料

c++核心準則英文原文(c++之父bjarne stroustrup的技術文章):

C 程式設計師應了解的那些事(7)虛函式的訪問控制

1 注意 基類的指標不能呼叫派生類自己定義的函式,只能呼叫虛函式!因為呼叫普通函式時是繫結靜態的,呼叫虛函式時才動態繫結。為了支援c 的多型性,才用了動態繫結和靜態繫結。理解他們的區別有助於更好的理解多型性,以及在程式設計的過程中避免犯錯誤。需要理解四個名詞 物件的靜態型別 物件在宣告時採用的型別,...

程式設計師那些事

摘自easy的 程式設計師跳槽全攻略 提公升架構能力 drydry是don t repeat yourself的縮寫,翻譯過來就是 不做重複事 這正是乙個逼近軟體本質的原則,它指導我們把經常使用的功能抽象成庫,把重複出現的 重構為可重用的框架模組。如果你用dry來要求自己,很快你就會發現自己抽象和架...

程式設計師那些事

展望未來,總結過去10年的程式設計師生涯,給程式設計師小弟弟小妹妹們的一些總結性忠告 走過的路,回憶起來是那麼曲折,把自己的一些心得體會分享給程式設計師兄弟姐妹們,雖然時代在變化,但是很可能你也會走我已經做過的10年的路程,有些心得體會你可以借鑑一下,覺得說得有道理的你就接納,覺得說得沒道理的,你就...