文章目錄
8.1 基本用法
對異常的處理:
- 引發異常;
- 使用異常處理程序(exception handler)捕捉異常;
- 使用try塊。
要在C++使用異常機制,可以在程序的try
塊內出現問題時使用throw
語句拋出異常(一個變量/常量)。
在try
塊中拋出的異常可以利用其後的catch
塊(異常處理程序)捕捉。
catch
塊通過識別拋出的異常類型捕捉異常(catch
塊類似於函數定義),並在執行完後返回到拋出異常的語句的下一個語句。
如果程序引發了異常而沒有在try
塊內或沒有匹配的處理程序(catch
塊)函數會調用abort()
函數,不過這一行爲可以修改。
8.2 將對象用作異常類型
通常,將對象作爲異常拋出。這樣做的重要優點之一是可以使用不同的異常類型來區分引發的問題。
8.3 棧解退
C++通常將調用函數的指令的地址(返回地址)放在棧中放在棧中。當被調用函數執行完畢(return
)時,程序利用此地址確定從哪裏繼續執行。
此外,若在函數中創建了自動變量,後者也會被放進棧中,若調用了一個函數,被調用函數的信息也會被存入棧中。每個函數在執行完後都會按壓棧的逆序釋放棧中的信息,直到釋放了第一個返回地址。
然而如果函數是因爲異常而非return
語句返回,則不會在釋放了第一個返回地址後停止釋放,而是會繼續釋放,直到釋放了第一個在try
塊內的返回地址。
然後會將控制權交給緊跟其後的catch
塊,而非接着運行函數調用的下一條語句。
在棧解退過程中,自動變量會被自動釋放,若自動變量是一個對象,那麼對象的析構函數也會被自動調用。
8.4 異常規範(不提倡使用)
double harm(double a) throw(bad_thing);
double marm(double a) throw();
異常規範出現於函數原型或定義中,可以包含類型列表(指出可能發生的異常,可以有多個),也可不包含(不會發生異常)。
要指出不會引發異常,還可以用C++11中的關鍵字noexcept
代替空類型列表的異常規範。
另外,還有運算符noexcept()
,可以判斷操作數是否會發生異常。
原則上,函數的異常規範應包含函數調用的函數的異常規範。
8.4 其他異常特徵
8.4.1 catch 塊使用引用
引發異常時編譯器總是會創建一個異常的臨時變量拷貝。這樣,catch
捕捉到的異常變量不是異常變量本身而只是一個副本,即使使用了引用。
但是使用引用有其他好處:
- 使用引用可以避免創建另一個臨時變量,減少不必要的開支。
- 如果使用基類的引用,那麼這個
catch
塊就還可以捕捉所有派生類類型的異常。不過如果要這麼做要將處於繼承層次上部的類型(如果想要特殊處理的話)放在靠後的位置。
8.4.2 默認異常處理程序
如果要捕捉未知類型的異常,使用類似以下這樣的try
塊:
catch(...)
{
cout << "undetermined error";
}
8.4.3 由於異常而引發的問題
- 異常在帶有異常規範的函數中引發,如果引發的異常不能與異常規範中的任何一個類型匹配,則稱這個異常爲意外異常(unexpected exception)。
- 異常不是在函數中引發的、函數沒有異常規範或異常符合異常規範,這時異常如果沒有被捕捉,則稱這個異常爲未捕捉異常(uncaught exception)。
上述函數是指與catch
塊在同一層及更深層的函數調用。
通常情況下,意外異常和未捕捉異常都會導致異常終止,但是可以修改這一行爲。
程序在發現以上兩種異常時並不會直接結束程序,而是會分別調用unexpected()
和terminate()
函數。默認情況下,terminate()
函數都會調用abort()
函數,而unexpected()
函數會調用terminate()
函數。
兩個函數聲明在頭文件exception
中。頭文件exception
中還聲明瞭set_unexpected()
和set_terminate()
函數,兩個函數都接受一個返回值類型爲void
,參數列表爲空的函數指針作爲參數。
函數指針也在exception
頭文件中進行了定義:
typedef void (*terminate_handler) ();
typedef void (*unexpected_handler) ();
相比於terminate_handler
函數,unexpected_handler
函數的行爲受到更嚴格的限制:
- 通過調用
abort()
、exit()
或terminate()
終止程序。 - 修改
unexpected()
,在unexpected()
中引發異常。- 引發的異常與原來的異常規範匹配,則函數按普通的方式正常運行,即尋找與新異常匹配的
catch
塊。 - 如果不匹配,且原來的異常規範中沒有包括
bad_exception
,異常程序會調用terminate()
函數,而不是再次調用unexpected()
函數。 - 如果·不匹配,但原來的異常規範中包括
bad_exception
,那麼不匹配的異常會自動被bad_exception
代替。
- 引發的異常與原來的異常規範匹配,則函數按普通的方式正常運行,即尋找與新異常匹配的
8.5 exception 類
異常 | 描述 |
---|---|
std::exception | 該異常是所有標準 C++ 異常的父類。 |
std::bad_alloc | 該異常可以通過 new 拋出。 |
std::bad_cast | 該異常可以通過 dynamic_cast 拋出。 |
std::bad_exception | 這在處理 C++ 程序中無法預期的異常時非常有用。 |
std::bad_typeid | 該異常可以通過 typeid 拋出。 |
std::logic_error | 理論上可以通過讀取代碼來檢測到的異常。 |
std::domain_error | 當使用了一個無效的數學域時,會拋出該異常。 |
std::invalid_argument | 當使用了無效的參數時,會拋出該異常。 |
std::length_error | 當創建了太長的 std::string 時,會拋出該異常。 |
std::out_of_range | 該異常可以通過方法拋出,例如 std::vector 和 std::bitset<>::operator[]()。 |
std::runtime_error | 理論上不可以通過讀取代碼來檢測到的異常。 |
std::overflow_error | 當發生數學上溢時,會拋出該異常。 |
std::range_error | 當嘗試存儲超出範圍的值時,會拋出該異常。 |
std::underflow_error | 當發生數學下溢時,會拋出該異常。 |
異常具有這樣的一種特性:類似於類,可以改變你的編程方式。
exception
頭文件中定義了exception
類,類中有虛方法what()
(除了構造函數和析構函數外,只有這個成員),what()
返回一個字符串。
因此可以以其爲基類派生出擁有用戶定義what()
函數的派生類。
8.5.1 stdexcept 異常類
頭文件stdexcept
定義了其他幾個異常類。
首先以公有繼承的方式從exception
派生出了logic_error
和runtime_error
類。
這些類的構造函數接受一個string
對象作爲參數,其what
函數接受一個C字符串。
ISO C++爲新版本的C++定義了一個接受C字符串的構造函數。
另外的信息可以參考頭文件。以下爲內容節選:
explicit
logic_error(const string& __arg) _GLIBCXX_TXN_SAFE;
#if __cplusplus >= 201103L
explicit
logic_error(const char*) _GLIBCXX_TXN_SAFE;
#endif
#if _GLIBCXX_USE_CXX11_ABI || _GLIBCXX_DEFINE_STDEXCEPT_COPY_OPS
logic_error(const logic_error&) _GLIBCXX_USE_NOEXCEPT;
logic_error& operator=(const logic_error&) _GLIBCXX_USE_NOEXCEPT;
#endif
// ...
virtual const char*
what() const _GLIBCXX_TXN_SAFE_DYN _GLIBCXX_USE_NOEXCEPT;
logic_error
又派生出了更具體的異常類:
domain_error
:建議用來處理數學的定義域問題。invalid_argument
:建議用來處理函數參數問題(內容問題)。length_error
:建議用來處理數組、動態分配的內存等長度不足的問題。out_of_range
:建議用來處理索引超出範圍的問題。
runtime_error
派生出了以下異常類:
range_error
:計算結果可能不在函數允許的範圍內但沒有發生上溢或下溢。overflow_error
:發生在浮點數計算中,一般來說,存在浮點類型可以表示的最大(絕對值)非零值,如果計算結果比這個值還大的話會發生下溢。underflow_error
:發生在浮點數計算中,一般來說,存在浮點類型可以表示的最小(絕對值)非零值,如果計算結果比這個值還小的話會發生下溢。
8.5.2 bad_alloc 異常和 new
在以前,new
請求內存出錯(通常是內存不足)時,new
返回一個空指針nullptr
。
但現在,new
會引發bad_alloc
異常。bad_alloc
異常和nothrow
都定義在頭文件new
中。
另外,在頭文件new
中還定義了bad_array_new_length
異常類型。
爲了儘量向下兼容,C++提供了返回空指針的new
:
int * pi = new (nothrow) int;
int * pi = new (nothrow) int[500];
8.6 有關異常的注意事項
- 應該在設計程序時就加入異常處理功能,而不是以後再添加。
- 異常規範不適用於模板,因爲模板函數引發的異常可能隨特定的具體化而異。
- 異常和動態內存分配並非總能協調工作。
對於第三點,一般有兩種解決方案:
- 在
catch
塊中添加相應地delete
語句。 - 使用智能指針取代普通指針來管理動態分配的內存。