处理异常
異常可以由處理塊進行處理。
處理塊
catch ( 屬性 (可選) 類型說明符序列 聲明符 ) 複合語句
|
(1) | ||||||||
catch ( 屬性 (可選) 類型說明符序列 抽象聲明符 (可選) ) 複合語句
|
(2) | ||||||||
catch ( ... ) 複合語句
|
(3) | ||||||||
| 屬性 | - | (C++11 起) 任意數量的屬性,應用到形參 |
| 類型說明符序列 | - | 形參聲明的一部分,與在函數形參列表中相同 |
| 聲明符 | - | 形參聲明的一部分,與在函數形參列表中相同 |
| 抽象聲明符 | - | 無名形參聲明的一部分,與在函數形參列表中相同 |
| 複合語句 | - | 複合語句 |
處理塊中的形參聲明描述了可以導致進入該處理塊的異常類型。
如果形參聲明為具有以下類型之一,那麼程序非良構:
|
(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)
{} // 如果 f() 抛出 std::overflow_error 就会执行它(“相同类型”规则)
catch (const std::runtime_error& e)
{} // 如果 f() 抛出 std::underflow_error 就会执行它(“基类”规则)
catch (const std::exception& e)
{} // 如果 f() 抛出 std::logic_error 就会执行它(“基类”规则)
catch (...)
{} // 如果 f() 抛出 std::string 或 int 或任何其他无关类型就会执行它
如果某個 try 塊的所有處理塊中沒有匹配的處理塊,那麼會對相同線程(C++11 起)的動態外圍 try 塊繼續查找匹配的處理塊。
如果沒有找到匹配的處理塊,那麼就會調用 std::terminate;由實現定義是否會在該 std::terminate 調用前進行棧回溯。
處理異常
在拋出異常時,控制會轉移到具有匹配類型的最近處理塊;這裡「最近」表示控制線程最近進入且尚未退出的 try 關鍵詞之後的符合語句或成員初始化器列表(如果存在)對應的最近處理塊。
初始化處理塊形參
形參列表中聲明的具有類型「可有 cv 限定的 T」或「到可有 cv 限定的 T 的左值引用」的形參(如果存在)會按以下方式從 E 類型的異常對象初始化:
- 如果
T是E的基類,那麼形參會從指定了異常對象的對應基類子對象的T類型左值複製初始化。 - 否則形參會從指定了異常對象的
E類型左值複製初始化。
形參的生存期會在處理塊退出時,並在處理塊中初始化的所有具有自動存儲期的對象析構後結束。
當聲明形參為對象時,修改該對象不會影響異常對象。
當聲明形參為到對象的引用時,修改被引用對象就是修改異常對象,並且在重新拋出該對象時也會生效。
激活處理塊
在處理塊的形參(如果存在)的初始化完成時,該處理塊進入活躍 狀態。
另外,因拋出異常而進入 std::terminate 時會將一個隱式的處理塊進入活躍狀態。
處理塊在退出時不再視為處於活躍狀態。
最近激活且處於活躍狀態中的處理塊匹配的異常被稱為當前正在處理的異常。這種異常可以重新拋出。
控制流
處理塊的複合語句 是有控制流限制的語句:
void f()
{
goto label; // 错误
try
{
goto label; // 错误
}
catch (...)
{
goto label: // OK
label: ;
}
}
註解
棧回溯會在轉移控制到處理塊的過程中發生。當處理塊進入活躍狀態時,棧回溯已經完成。
throw 表達式 throw 0 拋出的異常不會匹配指針或成員指針類型的處理塊。
|
(C++11 起) |
因為異常對象無法具有數組或函數類型,所以到數組或函數的引用類型的處理塊不會匹配任何異常對象。
有可能會寫出永遠無法執行的處理塊,比如將最終派生類型的處理塊放在對應的無歧義公開基類的處理塊後面:
try
{
f();
}
catch (const std::exception& e)
{} // 在 f() 抛出 std::runtime_error 时执行
catch (const std::runtime_error& e)
{} // 死代码!
許多實現將 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); // 应该通过,但在 MSVC 与 Clang 上失败
}
關鍵詞
示例
以下代碼演示處理塊的幾種用法:
#include <iostream>
#include <vector>
int main()
{
try
{
std::cout << "抛出整数异常...\n";
throw 42;
}
catch (int i)
{
std::cout << " 整数异常已捕获,它的值是:" << i << '\n';
}
try
{
std::cout << "创建一个大小为 5 的 vector...\n";
std::vector<int> v(5);
std::cout << "访问 vector 的第 11 个元素...\n";
std::cout << v.at(10); // vector::at() 会抛出 std::out_of_range
}
catch (const std::exception& e) // 按基类的引用捕获
{
std::cout << " 标准异常已捕获,它的信息是:'" << e.what() << "'\n";
}
}
可能的輸出:
抛出整数异常...
整数异常已捕获,它的值是:42
创建一个大小为 5 的 vector...
访问 vector 的第 11 个元素...
标准异常已捕获,它的信息是:'out_of_range'
缺陷報告
下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。
| 缺陷報告 | 應用於 | 出版時的行為 | 正確行為 |
|---|---|---|---|
| CWG 98 | C++98 | switch 語句可以轉移控制進入處理塊
|
已禁止 |
| CWG 210 | C++98 | 會以 throw 表達式來匹配處理塊
|
會以異常對象來匹配處理塊 |
| CWG 388 | C++98 | 指針或成員指針類型的異常不能為到不同類型的 const 引用匹配 | 使之在可轉換時可匹配 |
| CWG 1166 | C++98 | 未指明匹配到異常類型是到抽象類的引用類型的處理塊時的行為 | catch 子句不能對應抽象類類型 |
| CWG 1769 | C++98 | 當 catch 子句聲明的異常類型是異常對象的基類時, 該 處理塊的形參的初始化可能會用到轉換構造函數 |
此時該形參會從異常對象的 對應基類子對象複製初始化 |
| CWG 2093 | C++98 | 具有成員指針類型的異常對象無法通過限定性轉換匹配具有成員指針類型的處理塊 | 可以匹配 |
引用
- C++23 標準(ISO/IEC 14882:2024):
- 14.4 Handling an exception [except.handle]
- C++20 標準(ISO/IEC 14882:2020):
- 14.4 Handling an exception [except.handle]
- C++17 標準(ISO/IEC 14882:2017):
- 18.3 Handling an exception [except.handle]
- C++14 標準(ISO/IEC 14882:2014):
- 15.3 Handling an exception [except.handle]
- C++11 標準(ISO/IEC 14882:2011):
- 15.3 Handling an exception [except.handle]
- C++03 標準(ISO/IEC 14882:2003):
- 15.3 Handling an exception [except.handle]
- C++98 標準(ISO/IEC 14882:1998):
- 15.3 Handling an exception [except.handle]