共用方式為


錯誤和例外狀況處理 (現代 C++)

在現代 C++ 的大部分情況下,較好的邏輯及執行階段錯誤報告與處理方式就是使用例外狀況。 當偵測錯誤的函式與得知該錯誤處理方式內容的函式之間可能有數個函式呼叫包含在堆疊中時,更是如此 例外狀況為偵測錯誤的程式碼,提供正式、明確定義的方式來將資訊往呼叫堆疊上傳。

程式錯誤通常分為兩類:由程式設計錯誤造成的邏輯錯誤 (例如,「索引超出範圍」錯誤) 以及程式設計人員無法控制的執行階段錯誤 (例如,「網路服務無法使用」錯誤)。 在 C-Style 程式設計和 COM 之中,錯誤報告的管理方式是傳回表示特定函式錯誤碼或狀態碼的值,或是設定全域變數供呼叫端於每次呼叫函式後選擇性擷取,以查看是否有錯誤回報。 例如,COM 程式使用 HRESULT 傳回值,向呼叫端傳達錯誤,而 Win32 應用程式開發介面則有 GetLastError 函式,會擷取呼叫堆疊報告的最後錯誤。 在這兩種情況下,辨識程式碼和做出適當回應,都是由呼叫端來決定。 如果呼叫端未明確地處理錯誤碼,程式可能會損毀而不顯示警告,或繼續以錯誤資料執行和產生不正確的結果。

例外狀況是現代 C++ 中的慣用機制,原因如下:

  • 例外狀況會強制呼叫程式碼辨識錯誤條件和處理它。 未處理的例外狀況會停止程式執行。

  • 例外狀況跳至可以處理錯誤的呼叫堆疊上的點。 中繼函式可以讓例外狀況傳播。 它們不一定要是配合其他圖層。

  • 在擲回例外狀況後,例外狀況堆疊回溯機制會根據妥善定義的規則終結範圍中的所有物件。

  • 例外狀況允許在偵測錯誤的程式碼和處理錯誤的程式碼之間有清楚的分隔。

下列簡化範例顯示在 C++ 中擲回和攔截例外狀況所需的語法。

 
#include <stdexcept>
#include <limits>
#include <iostream>
 
using namespace std;
class MyClass
{
public:
   void MyFunc(char c)
   {
      if(c < numeric_limits<char>::max())
         throw invalid_argument("MyFunc argument too large.");
      //...
   }
};

int main()
{
   try
   {
      MyFunc(256); //cause an exception to throw
   }
 
   catch(invalid_argument& e)
   {
      cerr << e.what() << endl;
      return -1;
   }
   //...
   return 0;
}

在 C++ 中的例外狀況類似 C# 和 Java 等語言中的例外狀況。 在 try 區塊中,如果有例外狀況擲回,此例外狀況將會由類型符合該例外狀況之類型的第一個相關聯 catch 區塊攔截。 換句話說,執行會從 throw 陳述式跳至 catch 陳述式。 如果找不到可用的 catch 區塊,就會叫用 std::terminate 而且程式結束。 在 C++ 中,可以擲回任何類型;不過,建議您擲回直接或間接衍生自 std::exception 的類型。 在上述範例中,例外狀況類型 invalid_argument 是在 <stdexcept> 標頭檔的標準程式庫中定義。 C++ 不提供也不需要 finally 區塊,用來確認擲回例外狀況時釋放所有資源。 「資源擷取為初始設定」(RAII) 慣用語,使用智慧型指標,提供清除資源所需的必要功能。 如需詳細資訊,請參閱如何:例外狀況安全的設計。 如需 C++ 堆疊回溯機制的詳細資訊,請參閱C++ 中的例外狀況和堆疊回溯

基本方針

強固的錯誤處理在任何程式語言中都相當困難。 雖然例外狀況提供支援適當錯誤處理的功能,但是無法完成您的所有工作。 若要發揮例外狀況機制的優勢,請在設計程式碼時考量例外狀況。

  • 使用判斷提示來檢查永遠不應發生的錯誤。 使用例外狀況檢查可能發生的錯誤 (例如,公用函式參數的輸入驗證中的錯誤)。 如需詳細資訊,請參閱例外狀況與判斷提示的比較一節。

  • 當處理錯誤的程式碼可能與透過一個或多個中間函式呼叫偵測錯誤的程式碼分離時,請使用例外狀況。 當處理錯誤的程式碼與偵測它的程式碼緊密結合時,請考慮在重要效能迴圈中改用錯誤碼。 如需何時不要使用例外狀況的詳細資訊,請參閱When Not to Use Exceptions。

  • 對於可能擲回或傳播例外狀況的每一個函式,請提供三個例外狀況保證之一:強式保證、基本保證或 nothrow (noexcept) 保證。 如需詳細資訊,請參閱如何:例外狀況安全的設計

  • 以傳值方式擲回例外狀況,以傳址方式攔截這些例外狀況。 不要攔截您無法處理的例外狀況。 如需詳細資訊,請參閱擲回和攔截例外狀況 (C++) 的方針

  • 不要使用例外狀況規格,在 C++11 中它們已被取代。 如需詳細資訊,請參閱例外狀況規格和 noexcept 一節。

  • 在適用時使用標準程式庫例外狀況類型。 從 exception 類別階層衍生自訂例外狀況類型。 如需詳細資訊,請參閱How to: 使用標準的程式庫的例外狀況物件

  • 不允許例外狀況從解構函式或記憶體解除配置函式逸出。

例外狀況和效能

如果沒有擲回任何例外狀況,例外狀況機制的效能成本極微小。 如果擲回例外狀況,堆疊周遊和回溯的成本大致上相當於函式呼叫的成本。 在輸入 try 區塊之後,需要其他資料結構追蹤呼叫堆疊,如果擲回例外狀況,則需要附加指令回溯堆疊。 不過,在大部分情況下,效能和記憶體耗用量的成本並不顯著。 例外狀況對效能的負面影響可能只有在記憶體非常受限的系統上才值得注意,或者僅在可能經常發生錯誤、效能嚴重不足且處理錯誤程式碼緊密連結於報告錯誤程式碼的迴圈中變得顯著。 在任何情況下,不需要分析和測量,就無法知道例外狀況的實際成本。 即使在少數顯著成本的情況下,您可以根據增加正確性、更容易維護性,以及設計完善的例外狀況原則所提供的其他優點,進行衡量評估。

例外狀況與判斷提示的比較

例外狀況和判斷提示是用來在程式中偵測執行階段錯誤的兩個不同機制。 在開發期間使用判斷提示,測試如果所有程式碼皆正確時永遠不應為 true 的條件。 使用例外狀況處理這類錯誤沒有意義,因為錯誤顯示的是程式碼的某個部分必須修正,並不代表程式必須在執行階段復原的情況。 判斷提示會在陳述式停止執行,以便您可以在偵錯工具中檢查程式狀態,例外狀況從第一個適當的 catch 處理常式繼續執行。 即使您的程式碼是正確的,也請使用例外狀況檢查可能會在執行階段發生的錯誤狀況,例如「找不到檔案」或「記憶體不足」。您可能會想要從這些情況中復原,即使復原只是將訊息輸出至記錄並結束程式。 務必使用例外狀況,檢查公用函式的引數。 即使您的函式是無錯誤,您可能對使用者可能會傳遞給它的引數沒有完全控制。

C++ 例外狀況和 Windows SEH 例外狀況的比較

C 和 C++ 程式都可以使用 Windows 作業系統中的結構化例外狀況處理 (SEH) 機制。 SEH 的概念類似 C++ 例外狀況的概念,例外的是,SEH 使用的是 __try、__except 和 __finally 建構,而不是 try 和 catch。 在 Visual C++ 中,C++ 例外狀況是針對 SEH 來進行實作。 不過,當您撰寫 C++ 程式碼時,請使用 C++ 例外狀況語法。

如需 SEH 的詳細資訊,請參閱 結構化例外狀況處理 (C/C++)

例外狀況規格和 noexcept

例外狀況規格在 C++ 中引入,用來指定函式可能會擲回的例外狀況。 不過,例外狀況規格經證實有問題,在 C++11 草稿標準中已被取代。 除了對 throw() (表示例外狀況不允許任何例外狀況逸出) 以外,建議您不要使用例外狀況規格。 如果您必須使用類型 throw(type) 的例外狀況規格,請注意 Visual C++ 在某些方面偏離標準。 如需詳細資訊,請參閱例外狀況規格。 noexcept 規範是在 C++11 中引入做為 throw() 的慣用替代方案。

請參閱

概念

如何:例外狀況和非例外狀況代碼之間的介面

其他資源

歡迎回到 C++ (現代 C++)

C++ 語言參考

C++ 標準程式庫參考