異常

異常提供了一種透過將控制權轉移給稱為處理程式的特殊函式來響應程式中異常情況(如執行時錯誤)的方法。

為了捕獲異常,一部分程式碼被置於異常檢查之下。這是透過將該程式碼部分封裝在try塊中來完成的。當該塊中出現異常情況時,會丟擲一個異常,將控制權轉移給異常處理程式。如果沒有丟擲異常,程式碼將正常繼續,所有處理程式都將被忽略。

異常是透過在try塊內使用throw關鍵字丟擲的。異常處理程式使用catch關鍵字宣告,它必須緊跟在try塊之後。

// exceptions
#include <iostream>
using namespace std;

int main () {
  try
  {
    throw 20;
  }
  catch (int e)
  {
    cout << "An exception occurred. Exception Nr. " << e << '\n';
  }
  return 0;
}
An exception occurred. Exception Nr. 20

異常處理下的程式碼封裝在try塊中。在本例中,該程式碼只是丟擲一個異常。

1
throw 20;

throw表示式接受一個引數(在本例中為整數值20),該引數作為引數傳遞給異常處理程式。

異常處理程式在try塊的結束大括號之後立即使用catch關鍵字宣告。catch的語法類似於帶有單個引數的常規函式。此引數的型別非常重要,因為throw表示式傳遞的引數型別會與之進行檢查,只有當它們匹配時,該異常才會被該處理程式捕獲。

可以連結多個處理程式(即catch表示式);每個處理程式都具有不同的引數型別。只有引數型別與throw語句中指定的異常型別匹配的處理程式才會被執行。

如果使用省略號(...)作為catch的引數,該處理程式將捕獲任何異常,無論丟擲的異常型別是什麼。這可以作為預設處理程式,捕獲所有未被其他處理程式捕獲的異常。

1
2
3
4
5
6
try {
  // code here
}
catch (int param) { cout << "int exception"; }
catch (char param) { cout << "char exception"; }
catch (...) { cout << "default exception"; }

在這種情況下,最後一個處理程式將捕獲丟擲的任何型別不是int也不是char的異常。

異常被處理後,程式執行將在try-catch塊之後恢復,而不是在throw語句之後!。

也可以在更外部的try塊中巢狀try-catch塊。在這些情況下,我們有可能內部catch塊將異常轉發給其外部級別。這是透過不帶引數的throw;表示式完成的。例如。

1
2
3
4
5
6
7
8
9
10
11
try {
  try {
      // code here
  }
  catch (int n) {
      throw;
  }
}
catch (...) {
  cout << "Exception occurred";
}

異常規範

舊程式碼可能包含動態異常規範。它們在C++中已被棄用,但仍然支援。動態異常規範跟在函式的宣告之後,並附加一個throw說明符。例如。

1
double myfunction (char param) throw (int);

這聲明瞭一個名為myfunction的函式,該函式接受一個char型別的引數並返回一個double型別的值。如果該函式丟擲除int以外的任何型別的異常,該函式將呼叫std::unexpected,而不是查詢處理程式或呼叫std::terminate

如果此throw說明符為空,不帶型別,則表示任何異常都會呼叫std::unexpected。沒有throw說明符的函式(常規函式)從不呼叫std::unexpected,而是遵循查詢其異常處理程式的正常路徑。

1
2
int myfunction (int param) throw(); // all exceptions call unexpected
int myfunction (int param);         // normal exception handling 

標準異常

C++標準庫提供了一個專門用於宣告要作為異常丟擲的物件的基類。它稱為std::exception,定義在<exception>標頭檔案中。該類有一個名為what的虛成員函式,它返回一個以null結尾的字元序列(型別為char *),並且可以在派生類中被覆蓋,以包含某種異常描述。

// using standard exceptions
#include <iostream>
#include <exception>
using namespace std;

class myexception: public exception
{
  virtual const char* what() const throw()
  {
    return "My exception happened";
  }
} myex;

int main () {
  try
  {
    throw myex;
  }
  catch (exception& e)
  {
    cout << e.what() << '\n';
  }
  return 0;
}
My exception happened.

我們放置了一個處理程式,透過引用(注意型別後的&)來捕獲異常物件,因此這也捕獲了派生自exception的類,比如我們型別為myexceptionmyex物件。

C++標準庫元件丟擲的所有異常都丟擲派生自此類exception的異常。它們是:

exception描述
bad_allocnew在分配失敗時丟擲
bad_castdynamic_cast在動態轉換失敗時丟擲
bad_exception由某些動態異常說明符丟擲
bad_typeidtypeid丟擲
bad_function_call由空function物件丟擲
bad_weak_ptrshared_ptr在傳遞了錯誤的weak_ptr時丟擲

同樣派生自exception,標頭檔案<exception>定義了兩個通用的異常型別,可以被自定義異常繼承以報告錯誤。

exception描述
logic_error與程式內部邏輯相關的錯誤
runtime_error執行時檢測到的錯誤

需要檢查標準異常的一個典型例子是記憶體分配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// bad_alloc standard exception
#include <iostream>
#include <exception>
using namespace std;

int main () {
  try
  {
    int* myarray= new int[1000];
  }
  catch (exception& e)
  {
    cout << "Standard exception: " << e.what() << endl;
  }
  return 0;
}
 

在此示例中,異常處理程式可能捕獲的異常是bad_alloc。因為bad_alloc派生自標準基類exception,所以它可以被捕獲(透過引用捕獲,捕獲所有相關類)。
Index
目錄