丟擲異常
異常可以從throw 表示式丟擲,以下上下文也可能丟擲異常
目錄 |
[編輯] 異常物件
丟擲異常會初始化一個具有動態儲存期的物件,稱為異常物件。
如果異常物件的型別是以下型別之一,則程式是病態的
[編輯] 構造和析構異常物件
給定異常物件的型別為 T
- 令 obj 是型別為 const T 的左值,從 obj 複製初始化型別為
T
的物件必須是良構的。 - 如果
T
是類型別
異常物件的記憶體以未指定的方式分配。唯一保證是儲存永遠不會由全域性分配函式分配。
如果處理程式透過重新丟擲退出,則控制權傳遞給同一異常物件的另一個處理程式。在這種情況下,異常物件不會被析構。
當異常的最後一個剩餘活動處理程式以重新丟擲以外的任何方式退出時,異常物件被銷燬,並且實現可以以未指定的方式解除分配臨時物件的記憶體。 銷燬發生在處理程式中“引數列表”中宣告的物件銷燬之後。 |
(C++11 前) |
異常物件的潛在銷燬點是
在異常物件的所有潛在銷燬點中,有一個未指定的最後一個銷燬點,異常物件在該點被銷燬。所有其他點都發生在該最後一個點之前。然後,實現可以以未指定的方式解除分配異常物件的記憶體。 |
(C++11 起) |
[編輯] throw 表示式
throw expression |
(1) | ||||||||
throw
|
(2) | ||||||||
表示式 | - | 用於構造異常物件的表示式 |
當丟擲新異常時,其異常物件確定如下
- 異常物件的型別透過從 ex 的型別中移除任何頂層 cv 限定符來確定。
- 異常物件從 ex 複製初始化。
如果程式試圖在沒有異常正在處理時重新丟擲異常,則將呼叫std::terminate。否則,異常會使用現有異常物件重新啟用(不會建立新的異常物件),並且異常不再被認為是已捕獲的。
try { // throwing a new exception 123 throw 123; } catch (...) // catch all exceptions { // respond (partially) to exception 123 throw; // pass the exception to some other handler }
[編輯] 棧展開
異常物件構造完成後,控制流會向後(向上呼叫棧)工作,直到到達try 塊的開頭,此時將所有關聯處理程式的引數與異常物件的型別按出現順序進行比較,以找到匹配項。如果沒有找到匹配項,控制流將繼續展開棧,直到下一個try 塊,依此類推。如果找到匹配項,控制流將跳轉到匹配的處理程式。
隨著控制流向上呼叫棧,所有具有自動儲存期且在相應的try 塊進入後已構造但尚未銷燬的物件,其解構函式將按建構函式完成的逆序被呼叫。如果從區域性變數的解構函式或在return語句中使用的臨時變數的解構函式中丟擲異常,則還會呼叫從函式返回的物件的解構函式。
如果從物件的建構函式或(罕見地)從解構函式中丟擲異常(無論物件的儲存期如何),則所有已完全構造的非靜態非變體成員和基類的解構函式將按其建構函式完成的逆序被呼叫。類聯合的變體成員只在建構函式展開的情況下被銷燬,並且如果在初始化和銷燬之間活動成員發生了變化,則行為是未定義的。
如果委託建構函式在非委託建構函式成功完成後以異常退出,則會呼叫此物件的解構函式。 |
(C++11 起) |
如果異常是從new-expression呼叫的建構函式中丟擲的,則如果可用,會呼叫匹配的解除分配函式。
此過程稱為棧展開。
如果在異常物件初始化之後、異常處理程式開始之前,任何由棧展開機制直接呼叫的函式以異常退出,則會呼叫std::terminate。此類函式包括超出作用域的具有自動儲存期物件的解構函式,以及(如果未省略)為初始化按值捕獲的引數而呼叫的異常物件的複製建構函式。
如果丟擲異常但未捕獲,包括逃逸std::thread的初始函式、main函式以及任何靜態或執行緒區域性物件的建構函式或解構函式的異常,則會呼叫std::terminate。對於未捕獲的異常是否進行任何棧展開是實現定義的。
[編輯] 注意
重新丟擲異常時,必須使用第二種形式以避免在異常物件使用繼承(典型情況)時發生物件切片
try { std::string("abc").substr(10); // throws std::out_of_range } catch (const std::exception& e) { std::cout << e.what() << '\n'; // throw e; // copy-initializes a new exception object of type std::exception throw; // rethrows the exception object of type std::out_of_range }
throw 表示式被分類為型別為void的prvalue 表示式。與任何其他表示式一樣,它可能是另一個表示式中的子表示式,最常見的是在條件運算子中
double f(double d) { return d > 1e7 ? throw std::overflow_error("too big") : d; } int main() { try { std::cout << f(1e10) << '\n'; } catch (const std::overflow_error& e) { std::cout << e.what() << '\n'; } }
[編輯] 關鍵詞
[編輯] 示例
#include <iostream> #include <stdexcept> struct A { int n; A(int n = 0): n(n) { std::cout << "A(" << n << ") constructed successfully\n"; } ~A() { std::cout << "A(" << n << ") destroyed\n"; } }; int foo() { throw std::runtime_error("error"); } struct B { A a1, a2, a3; B() try : a1(1), a2(foo()), a3(3) { std::cout << "B constructed successfully\n"; } catch(...) { std::cout << "B::B() exiting with exception\n"; } ~B() { std::cout << "B destroyed\n"; } }; struct C : A, B { C() try { std::cout << "C::C() completed successfully\n"; } catch(...) { std::cout << "C::C() exiting with exception\n"; } ~C() { std::cout << "C destroyed\n"; } }; int main () try { // creates the A base subobject // creates the a1 member of B // fails to create the a2 member of B // unwinding destroys the a1 member of B // unwinding destroys the A base subobject C c; } catch (const std::exception& e) { std::cout << "main() failed to create C with: " << e.what(); }
輸出
A(0) constructed successfully A(1) constructed successfully A(1) destroyed B::B() exiting with exception A(0) destroyed C::C() exiting with exception main() failed to create C with: error
[編輯] 缺陷報告
下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。
缺陷報告 | 應用於 | 釋出時的行為 | 正確的行為 |
---|---|---|---|
CWG 499 | C++98 | 未知邊界的陣列不能被丟擲,因為 其型別不完整,但異常物件可以 從衰減的指標建立,沒有任何問題 |
將型別完成要求 應用於異常物件 代替 |
CWG 668 | C++98 | 如果從區域性非自動物件的解構函式中丟擲異常,則不呼叫std::terminate。 從區域性非自動物件的解構函式中 |
呼叫std::terminate 在這種情況下 |
CWG 1863 | C++11 | 當丟擲僅可移動的異常物件時,不需要複製建構函式,但稍後允許複製 丟擲時,但允許稍後複製 |
要求複製建構函式 |
CWG 1866 | C++98 | 從建構函式棧展開時,變體成員被洩露 | 變體成員已銷燬 |
CWG 2176 | C++98 | 從區域性變數解構函式丟擲 可能會跳過返回值解構函式 |
函式返回值 已新增到展開中 |
CWG 2699 | C++98 | throw "EX" 實際上會丟擲 char* 而不是 const char* | 已更正 |
CWG 2711 | C++98 | 未指定異常物件的複製初始化源 異常物件未指定 |
從 expression 複製初始化 |
CWG 2775 | C++98 | 異常物件複製初始化要求不明確 | 已明確 |
CWG 2854 | C++98 | 異常物件的儲存期不明確 | 已明確 |
P1825R0 | C++11 | 禁止在throw 中從引數隱式移動 |
允許 |
[編輯] 參考文獻
- C++23 標準 (ISO/IEC 14882:2024)
- 7.6.18 丟擲異常 [expr.throw]
- 14.2 丟擲異常 [except.throw]
- C++20 標準 (ISO/IEC 14882:2020)
- 7.6.18 丟擲異常 [expr.throw]
- 14.2 丟擲異常 [except.throw]
- C++17 標準 (ISO/IEC 14882:2017)
- 8.17 丟擲異常 [expr.throw]
- 18.1 丟擲異常 [except.throw]
- C++14 標準 (ISO/IEC 14882:2014)
- 15.1 丟擲異常 [except.throw]
- C++11 標準 (ISO/IEC 14882:2011)
- 15.1 丟擲異常 [except.throw]
- C++03 標準 (ISO/IEC 14882:2003)
- 15.1 丟擲異常 [except.throw]
- C++98 標準 (ISO/IEC 14882:1998)
- 15.1 丟擲異常 [except.throw]
[編輯] 另請參閱
(C++17 前) |