Handling exceptions
例外は exception ハンドラによって処理できます。
目次 |
Handler
catch
(
attr
(オプション)
type-specifier-seq
declarator
)
compound-statement
|
(1) | ||||||||
catch
(
attr
(オプション)
type-specifier-seq
abstract-declarator
(オプション)
)
compound-statement
|
(2) | ||||||||
catch
(
...
)
compound-statement
|
(3) | ||||||||
| attr | - | (since C++11) 任意の数の attributes (パラメータに適用) |
| type-specifier-seq | - | 仮パラメータ宣言の一部。関数の parameter list と同様 |
| declarator | - | パラメータ宣言の一部。関数の parameter list と同様 |
| abstract-declarator | - | 無名パラメータ宣言の一部。関数の parameter list と同様 |
| compound-statement | - | compound statement |
ハンドラ内のパラメータ宣言は、そのハンドラが実行される原因となる例外の型を記述します。
パラメータが以下のいずれかの型で宣言されている場合、プログラムは不適格です:
| (C++11以降) |
- (おそらくcv修飾された可能性のある) void 以外の不完全型へのポインタ
- 不完全型への左辺値参照
パラメータが「
T
の配列」または関数型
T
として宣言されている場合、その型は「
T
へのポインタ」に調整されます。
パラメータ型
T
を持つハンドラは、「型
T
のハンドラ」と略記できます。
例外のマッチング
各 try ブロックは複数のハンドラと関連付けられ、これらのハンドラはハンドラシーケンスを形成します。例外が try ブロックからスローされると、シーケンス内のハンドラは出現順に例外との一致が試みられます。
ハンドラは、以下のいずれかの条件が満たされる場合、型
E
の
例外オブジェクト
にマッチします:
-
ハンドラは「修飾される可能性のある cv 修飾子付き
T」型、または「修飾される可能性のある cv 修飾子付きTへの左辺値参照」型であり、以下のいずれかの条件が満たされる:
-
-
EとTは同じ型である(トップレベルのcv修飾子を無視する)。 -
TはEの明確なpublic基底クラスである。
-
-
ハンドラは「修飾の可能性がある
T」または const T & 型であり、ここでTはポインタ型またはメンバへのポインタ型であり、以下の条件のいずれかを満たす:
-
-
Eは、以下の変換の少なくとも1つによってTに変換可能なポインタ型またはメンバへのポインタ型です:
-
- 非公開クラス、保護クラス、または曖昧なクラスへのポインタ変換を含まない 標準ポインタ変換
-
| (C++17以降) |
|
(C++11以降) |
catch ( ... ) ハンドラはあらゆる型の例外にマッチします。存在する場合、ハンドラシーケンスの最後のハンドラにのみなれます。このハンドラは、 nothrow例外保証 を提供する関数から未捕捉の例外が確実に外部へ伝播しないようにするために使用できます。
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 ブロックのハンドラ群で一致するものが見つからない場合、一致するハンドラの検索は動的に外側を囲む try ブロックで継続されます (同じスレッド内で) (C++11以降) 。
一致するハンドラが見つからない場合、 std::terminate が呼び出される。この std::terminate の呼び出しの前に スタックの巻き戻し が行われるかどうかは実装定義である。
例外処理
例外がスローされると、制御は一致する型を持つ最も近いハンドラに転送されます。「最も近い」とは、スレッドの制御フローが最も最近に入り、まだ抜けていない try キーワードに続く複合文またはメンバ初期化子リスト(存在する場合)に対応するハンドラを意味します。
ハンドラパラメータの初期化
パラメータリストで宣言されたパラメータ(もしあれば)は、「possibly cv-qualified
T
」型または「possibly cv-qualified
T
へのlvalue参照」型であり、以下のように
exception object
から初期化されます。exception objectの型は
E
です:
-
TがEの基底クラスである場合、パラメータは例外オブジェクトの対応する基底クラス部分オブジェクトを指す型Tの左辺値から コピー初期化 されます。 -
それ以外の場合、パラメータは例外オブジェクトを指す型
Eの左辺値からコピー初期化されます。
パラメータの寿命は、ハンドラ内で初期化された自動 storage duration を持つオブジェクトの破棄後、ハンドラが終了するときに終了します。
パラメータがオブジェクトとして宣言されている場合、そのオブジェクトへの変更は例外オブジェクトに影響しません。
パラメータがオブジェクトへの参照として宣言されている場合、参照先オブジェクトへの変更は例外オブジェクトへの変更となり、そのオブジェクトが再スローされた場合に効果を発揮します。
ハンドラの起動
ハンドラは、ハンドラのパラメータ(存在する場合)の初期化が完了したときに active と見なされます。
また、スローによって std::terminate が呼び出された場合にも、暗黙のハンドラはアクティブであると見なされます。
ハンドラは、ハンドラが終了した時点でアクティブとは見なされなくなります。
現在もアクティブなハンドラの中で最も最近にアクティブ化された例外は、 currently handled exception と呼ばれます。このような例外は rethrown することができます。
制御フロー
ハンドラーの compound-statement は control-flow-limited statement です:
void f() { goto label; // エラー try { goto label; // エラー } catch (...) { goto label: // OK label: ; } }
注記
Stack unwinding は、制御がハンドラに転送される間に発生します。ハンドラがアクティブになると、スタックの巻き戻しは既に完了しています。
throw 式によってスローされた例外は、 throw 0 ポインタ型またはポインタ-to-member型のハンドラと一致しません。
|
(C++11以降) |
Exception objects は配列型または関数型を持つことは決してないため、配列または関数型への参照を扱うハンドラは、いかなる例外オブジェクトとも一致しません。
ハンドラが決して実行されないように記述することが可能です。例えば、最終派生クラスのハンドラを、対応する明確な公開基底クラスのハンドラの後に配置する場合などです:
try { f(); } catch (const std::exception& e) {} // f()がstd::runtime_errorをスローした場合に実行される catch (const std::runtime_error& e) {} // デッドコード!
多くの実装は、非constポインタ型への参照のハンドラに対して CWG issue 388 の解決策を過度に拡張しています:
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 << "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++規格に対して遡及的に適用されました。
| DR | 適用対象 | 公開時の動作 | 正しい動作 |
|---|---|---|---|
| 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]