處理異常
異常可以由處理程式處理。
目錄 |
[編輯] 處理程式
catch ( attr (可選) type-specifier-seq declarator ) compound-statement |
(1) | ||||||||
catch ( attr (可選) type-specifier-seq abstract-declarator (可選) ) compound-statement |
(2) | ||||||||
catch ( ... ) compound-statement |
(3) | ||||||||
屬性 | - | (C++11 起) 任意數量的屬性,應用於引數 |
type-specifier-seq | - | 形式引數宣告的一部分,與函式引數列表中相同 |
宣告符 | - | 引數宣告的一部分,與函式引數列表中相同 |
abstract-declarator | - | 未命名引數宣告的一部分,與函式引數列表中相同 |
複合語句 | - | 複合語句 |
處理程式中的引數宣告描述了可能導致該處理程式進入的異常型別。
如果引數被宣告為以下型別之一,則程式格式錯誤
|
(C++11 起) |
- 指向非(可能帶cv限定的)void 的不完整型別的指標
- 對不完整型別的左值引用
如果引數被宣告為“T 陣列”型別或函式型別 T,則該型別被調整為“指向 T 的指標”。
引數型別為 T 的處理程式可以簡寫為“T 型別處理程式”。
[編輯] 匹配異常
每個 try 塊都關聯著許多處理程式,這些處理程式形成一個處理程式序列。當從 try 塊丟擲異常時,序列中的處理程式按出現的順序嘗試匹配該異常。
如果滿足以下任何條件,則處理程式與型別為 E
的異常物件匹配
- 處理程式的型別是“可能是 cv 限定的
T
”或“對可能是 cv 限定的T
的左值引用”,並且滿足以下任何條件
-
E
和T
是相同的型別(忽略頂層 cv 限定符)。 -
T
是E
的明確的公共基類。
-
- 處理程式的型別是“可能是 cv 限定的
T
”或 const T&,其中T
是指標或指向成員的指標型別,並且滿足以下任何條件
-
E
是指標或指向成員的指標型別,可以透過以下至少一種轉換轉換為T
- 不涉及轉換為指向私有、保護或模糊類的指標的標準指標轉換。
-
(C++17 起) |
|
(C++11 起) |
catch (...) 處理程式匹配任何型別的異常。如果存在,它只能是處理程式序列中的最後一個處理程式。此處理程式可用於確保不會有任何未捕獲的異常從提供不丟擲異常保證的函式中逃逸。
try { f(); } catch (const std::overflow_error& e) {} // this executes if f() throws std::overflow_error (same type rule) catch (const std::runtime_error& e) {} // this executes if f() throws std::underflow_error (base class rule) catch (const std::exception& e) {} // this executes if f() throws std::logic_error (base class rule) catch (...) {} // this executes if f() throws std::string or int or any other unrelated type
如果 try 塊的處理程式中沒有找到匹配項,則會繼續在動態上包圍的 try 塊的同一執行緒中(C++11 起)搜尋匹配的處理程式。
如果未找到匹配的處理程式,則呼叫 std::terminate;在呼叫 std::terminate 之前堆疊是否展開是實現定義的。
[編輯] 處理異常
當丟擲異常時,控制權會轉移到具有匹配型別的最近的處理程式;“最近”是指其複合語句或成員初始化列表(如果存在)緊跟在 try 關鍵字之後,且最近由控制執行緒進入但尚未退出的處理程式。
[編輯] 初始化處理程式引數
引數列表中宣告的引數(如果存在),型別為“可能是 cv 限定的 T
”或“對可能是 cv 限定的 T
的左值引用”,按如下方式從型別為 E
的異常物件初始化
- 如果
T
是E
的基類,則引數透過複製初始化從型別為T
的左值初始化,該左值指定異常物件的相應基類子物件。 - 否則,引數透過複製初始化從型別為
E
的左值初始化,該左值指定異常物件。
引數的生命週期在處理程式退出時結束,即在處理程式中初始化的任何具有自動儲存期的物件被銷燬之後。
當引數宣告為物件時,對該物件的任何更改都不會影響異常物件。
當引數宣告為物件的引用時,對引用物件的任何更改都是對異常物件的更改,並且如果該物件被重新丟擲,則將生效。
[編輯] 啟用處理程式
當處理程式的引數(如果存在)初始化完成時,處理程式被認為是活動的。
此外,當因丟擲異常而進入 std::terminate 時,隱式處理程式被認為是活動的。
當處理程式退出時,處理程式不再被認為是活動的。
具有最近啟用且仍活動的處理程式的異常稱為當前處理的異常。此類異常可以被重新丟擲。
[編輯] 控制流
處理程式的 compound-statement 是一個控制流受限語句
void f() { goto label; // error try { goto label; // error } catch (...) { goto label: // OK label: ; } }
[編輯] 注意
堆疊展開發生在控制權轉移到處理程式期間。當處理程式變為活動狀態時,堆疊展開已經完成。
由 throw 0 的 throw 表示式丟擲的異常不匹配指標或指向成員的指標型別的處理程式。
|
(C++11 起) |
異常物件永遠不能具有陣列或函式型別,因此對陣列或函式型別的引用的處理程式永遠不會匹配任何異常物件。
可以編寫永遠不會執行的處理程式,例如,將最終派生類的處理程式放置在相應明確公共基類的處理程式之後
try { f(); } catch (const std::exception& e) {} // will be executed if f() throws std::runtime_error catch (const std::runtime_error& e) {} // dead code!
許多實現過度擴充套件了 CWG 問題 388 的解決方案,使其適用於對非 const 指標型別的引用處理程式
int i; try { try { throw static_cast<float*>(nullptr); } catch (void*& pv) { pv = &i; throw; } } catch (const float* pf) { assert(pf == nullptr); // should pass, but fails on MSVC and Clang }
[編輯] 關鍵詞
[編輯] 示例
以下示例演示了處理程式的幾種用法
#include <iostream> #include <vector> int main() { try { std::cout << "Throwing an integer exception...\n"; throw 42; } catch (int i) { std::cout << " the integer exception was caught, with value: " << i << '\n'; } try { std::cout << "Creating a vector of size 5... \n"; std::vector<int> v(5); std::cout << "Accessing the 11th element of the vector...\n"; std::cout << v.at(10); // vector::at() throws std::out_of_range } catch (const std::exception& e) // caught by reference to base { std::cout << " a standard exception was caught, with message: '" << e.what() << "'\n"; } }
可能的輸出
Throwing an integer exception... the integer exception was caught, with value: 42 Creating a vector of size 5... Accessing the 11th element of the vector... a standard exception was caught, with message: 'out_of_range'
[編輯] 缺陷報告
下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。
缺陷報告 | 應用於 | 釋出時的行為 | 正確的行為 |
---|---|---|---|
CWG 98 | C++98 | 一個 switch 語句可以將控制權轉移到處理程式中 | 已禁止 |
CWG 210 | C++98 | throw 表示式與處理程式匹配 | 異常物件是 與處理程式匹配 |
CWG 388 | C++98 | 指標或指向成員的指標型別的異常可能 不匹配對不同型別的 const 引用 |
可匹配 當可轉換時 |
CWG 1166 | C++98 | 當處理程式的型別是對抽象類型別的引用時,行為未指定 抽象類型別被匹配 |
抽象類型別 不允許用於處理程式 |
CWG 1769 | C++98 | 當處理程式的型別是異常物件型別的基類時,轉換建構函式可能 用於處理程式引數的初始化 引數被複制初始化 |
從相應的基類 異常物件的子物件 異常物件的相應基類子物件 |
CWG 2093 | C++98 | 指向物件型別的異常物件無法透過限定符轉換匹配指向物件型別的處理程式 指向物件型別的處理程式 |
允許 |
[編輯] 參考資料
- C++23 標準 (ISO/IEC 14882:2024)
- 14.4 處理異常 [except.handle]
- C++20 標準 (ISO/IEC 14882:2020)
- 14.4 處理異常 [except.handle]
- C++17 標準 (ISO/IEC 14882:2017)
- 18.3 處理異常 [except.handle]
- C++14 標準 (ISO/IEC 14882:2014)
- 15.3 處理異常 [except.handle]
- C++11 標準 (ISO/IEC 14882:2011)
- 15.3 處理異常 [except.handle]
- C++03 標準 (ISO/IEC 14882:2003)
- 15.3 處理異常 [except.handle]
- C++98 標準 (ISO/IEC 14882:1998)
- 15.3 處理異常 [except.handle]