拋出例外
拋出 例外 (exception) 會將控制權轉移給 處理常式 (handler)。
例外可以從 throw 表達式拋出,以下情況也可能拋出例外:
目錄 |
[編輯] 例外物件
拋出例外會初始化一個具有動態 儲存期 (storage duration) 的物件,稱為 例外物件 (exception object)。
如果例外物件的類型是以下類型之一,則程式格式錯誤:
- 不完整類型 (incomplete type)
- 抽象類別類型 (abstract class type)
- 指向不完整類型的指標(非 void,可包含 cv 限定符)
[編輯] 例外物件的建構與解構
給定例外物件的類型為 T
- 設 obj 為類型 const T 的左值,則從 obj 複製初始化 (copy-initialization) 一個類型為
T的物件必須是合法的。 - 如果
T是類別類型:
- 所選的 建構函式 必須是 ODR 使用 (odr-used) 的。
T的 解構函式 被 潛在呼叫 (potentially invoked)。
例外物件的記憶體以未指定的方式配置。唯一保證的是,該儲存空間絕不會由全域 配置函式 (allocation functions) 分配。
如果一個 處理常式 透過 重新拋出 (rethrowing) 退出,控制權會傳遞給同一個例外物件的另一個處理常式。在此情況下,不會解構該例外物件。
|
當該例外最後一個存活的處理常式以非重新拋出的方式退出時,例外物件將被銷毀,實作可以未指定的方式釋放該臨時物件的記憶體。 銷毀發生在處理常式中「參數列表」所宣告的物件被銷毀之後。 |
(直到 C++11) |
|
例外物件的潛在銷毀點如下:
在所有例外物件的潛在銷毀點中,有一個未指定的最後一個點,此時例外物件會被銷毀。所有其他點都 先於 (happen before) 該最後一個點。隨後實作可以以未指定的方式釋放例外物件的記憶體。 |
(C++11 起) |
[編輯] throw 表達式
throw 表達式 |
(1) | ||||||||
throw
|
(2) | ||||||||
| expression | - | 用於建構例外物件的表達式 |
當拋出新例外時,其例外物件的確定方式如下:
- 例外物件的類型是透過移除 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 區塊進入以來已建構但尚未銷毀的所有具有 自動儲存期 (automatic storage duration) 物件的解構函式,呼叫順序為建構完成順序的逆序。如果從局部變數的解構函式或 return 陳述式中使用的臨時物件解構函式拋出例外,則會呼叫從該函式返回物件的解構函式。
如果從物件的建構函式或(罕見的)解構函式中拋出例外(無論該物件的儲存期為何),會呼叫所有已完整建構的非靜態、非變體成員及基底類別的解構函式,呼叫順序為建構完成順序的逆序。類聯集類別 (union-like classes) 的變體成員僅在從建構函式解開堆疊時銷毀,若在初始化與銷毀之間變更了活動成員,則行為未定義。
|
如果委派建構函式 (delegating constructor) 在非委派建構函式成功完成後以例外退出,則會呼叫該物件的解構函式。 |
(C++11 起) |
如果例外是由 new 表達式 呼叫的建構函式所拋出,則會呼叫對應的 記憶體釋放函式 (deallocation function)(若有提供)。
此過程稱為 堆疊解開 (stack unwinding)。
如果在例外物件初始化之後且例外處理常式開始之前,任何由堆疊解開機制直接呼叫的函式以例外退出,則會呼叫 std::terminate。此類函式包括:已退出作用域的自動儲存期物件的 解構函式,以及(若未進行 複製消除)為初始化傳值捕獲參數而呼叫的例外物件複製建構函式。
如果拋出的例外未被捕獲(包含脫離 std::thread 初始函式、main 函式,以及任何靜態或執行緒局部物件的建構函式或解構函式的例外),則會呼叫 std::terminate。對於未捕獲的例外是否進行堆疊解開,由實作定義。
[編輯] 附註
在重新拋出例外時,必須使用第二種形式以避免在例外物件使用繼承的(典型)情況下發生物件切割 (object slicing)。
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 expression)。與任何其他表達式一樣,它可以作為另一個表達式的子表達式,最常見的是在 條件運算子 中。
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++ 標準。
| DR | 應用於 | 出版時的行為 | 正確的行為 |
|---|---|---|---|
| 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 | 複製初始化的來源 例外物件未被指定 |
從 表達式 複製初始化 |
| CWG 2775 | C++98 | 例外物件複製初始化要求不明確 | 現已明確化 |
| CWG 2854 | C++98 | 例外物件的儲存期不明確 | 現已明確化 |
| P1825R0 | C++11 | 在 throw 中禁止隱式從參數移動 |
已允許 |
[編輯] 參考資料
- C++23 標準 (ISO/IEC 14882:2024)
- 7.6.18 Throwing an exception [expr.throw]
- 14.2 Throwing an exception [except.throw]
- C++20 標準 (ISO/IEC 14882:2020)
- 7.6.18 Throwing an exception [expr.throw]
- 14.2 Throwing an exception [except.throw]
- C++17 標準 (ISO/IEC 14882:2017)
- 8.17 Throwing an exception [expr.throw]
- 18.1 Throwing an exception [except.throw]
- C++14 標準 (ISO/IEC 14882:2014)
- 15.1 Throwing an exception [except.throw]
- C++11 標準 (ISO/IEC 14882:2011)
- 15.1 Throwing an exception [except.throw]
- C++03 標準 (ISO/IEC 14882:2003)
- 15.1 Throwing an exception [except.throw]
- C++98 標準 (ISO/IEC 14882:1998)
- 15.1 Throwing an exception [except.throw]
[編輯] 參見
| (直到 C++17) |