Throwing exceptions
例外を throw すると、制御が handler に転送されます。
例外は throw 式 からスローされることがありますが、以下のコンテキストでも例外がスローされる可能性があります:
目次 |
例外オブジェクト
例外をスローすると、動的 storage duration を持つオブジェクトが初期化され、これは exception object と呼ばれます。
例外オブジェクトの型が以下のいずれかの型である場合、プログラムは不適格となります:
例外オブジェクトの構築と破棄
例外オブジェクトの型が
T
である場合:
-
obj
を型
const
T
の左辺値とすると、型
Tのオブジェクトの コピー初期化 が obj から適正に行われなければならない。 -
Tがクラス型の場合:
-
- 選択された constructor は odr-used されます。
-
Tの destructor は potentially invoked されます。
例外オブジェクトのメモリは未規定の方法で確保されます。唯一の保証は、その記憶域がグローバルな allocation functions によって決して確保されないことです。
もし ハンドラ が 再スロー によって終了する場合、制御は同じ例外オブジェクトに対する別のハンドラに渡されます。この場合、例外オブジェクトは破棄されません。
|
例外に対する最後に残ったアクティブなハンドラが再スロー以外の方法で終了した場合、例外オブジェクトは破棄され、実装は一時オブジェクトのメモリを未規定の方法で解放してもよい。 破棄は、ハンドラの「パラメータリスト」で宣言されたオブジェクトの破棄の直後に発生する。 |
(C++11まで) |
|
例外オブジェクトの潜在的な破棄ポイントは以下の通り:
例外オブジェクトの全ての潜在的な破棄ポイントの中で、例外オブジェクトが破棄される未規定の最後のポイントが存在する。他の全てのポイントは その最後のポイントより前に発生する 。実装はその後、例外オブジェクトのメモリを未規定の方法で解放してもよい。 |
(C++11以降) |
throw 式
throw
式
|
(1) | ||||||||
throw
|
(2) | ||||||||
| expression | - | 例外オブジェクトの構築に使用される式 |
新しい例外がスローされる際、その例外オブジェクトは以下のように決定されます:
- 配列からポインタへの変換 および 関数からポインタへの変換 標準変換が expression に対して実行されます。
- ex を変換結果とします:
-
-
- 例外オブジェクトの型は、 ex の型から最上位のcv修飾子をすべて除去することで決定される。
- 例外オブジェクトは copy-initialized され、 ex から初期化される。
-
プログラムが現在処理中の例外がない状態で例外を再スローしようとすると、 std::terminate が呼び出されます。それ以外の場合、既存の例外オブジェクトで例外が再活性化され(新しい例外オブジェクトは作成されません)、その例外はもはや捕捉されたとは見なされません。
try { // 新しい例外123をスロー throw 123; } catch (...) // すべての例外をキャッチ { // 例外123に(部分的に)対応 throw; // 例外を他のハンドラに渡す }
スタックアンワインディング
例外オブジェクトが構築されると、制御フローは逆向き(コールスタックを遡る)に動作し、 try ブロック の開始地点に到達する。この時点で、関連するすべてのハンドラのパラメータが例外オブジェクトの型と順次比較され、 一致 が検索される。一致が見つからない場合、制御フローはスタックの巻き戻しを続け、次の try ブロックに到達するまでこれを繰り返す。一致が見つかった場合、制御フローは対応するハンドラにジャンプする。
制御フローがコールスタックを上昇する際、対応する try ブロックが開始されてから構築されたがまだ破棄されていない全ての automatic storage duration を持つオブジェクトに対して、それらのコンストラクタの完了順序と逆順でデストラクタが呼び出されます。ローカル変数や return 文で使用される一時オブジェクトのデストラクタから例外がスローされた場合、関数から返されるオブジェクトのデストラクタも呼び出されます。
オブジェクトのコンストラクタまたは(稀に)デストラクタから例外がスローされた場合(オブジェクトのストレージ期間に関わらず)、完全に構築された非静的non-variantメンバおよび基底クラスのデストラクタが、それらのコンストラクタの完了とは逆順で呼び出されます。 union-likeクラス のvariantメンバは、コンストラクタからのアンワインディングの場合にのみ破棄され、初期化と破棄の間にアクティブメンバが変更された場合、動作は未定義です。
|
非委譲コンストラクタが正常に完了した後に委譲コンストラクタが例外で終了する場合、このオブジェクトのデストラクタが呼び出されます。 |
(since C++11) |
例外が new式 によって呼び出されるコンストラクタからスローされた場合、利用可能であれば対応する デアロケーション関数 が呼び出されます。
このプロセスは stack unwinding と呼ばれます。
スタック巻き戻し機構によって直接呼び出される関数が、例外オブジェクトの初期化後かつ例外ハンドラの開始前に、例外によって終了した場合、 std::terminate が呼び出されます。このような関数には、スコープを抜ける自動ストレージ期間を持つオブジェクトの デストラクタ 、および( 省略 されない場合)値によるキャッチ引数を初期化するために呼び出される例外オブジェクトのコピーコンストラクタが含まれます。
例外がスローされ、キャッチされなかった場合( std::thread の初期関数、main関数、および静的オブジェクトまたはスレッドローカルオブジェクトのコンストラクタやデストラクタから例外が抜け出る場合を含む)、 std::terminate が呼び出されます。キャッチされない例外に対してスタックアンワインディングが行われるかどうかは実装定義です。
注記
例外を再スローする際には、例外オブジェクトが継承を使用する(典型的な)ケースでオブジェクトスライシングを回避するために、2番目の形式を使用する必要があります:
try { std::string("abc").substr(10); // std::out_of_rangeをスロー } catch (const std::exception& e) { std::cout << e.what() << '\n'; // throw e; // std::exception型の新しい例外オブジェクトをコピー初期化 throw; // std::out_of_range型の例外オブジェクトを再スロー }
throw 式は、 prvalue式 として分類され、型は void です。他のあらゆる式と同様に、別の式の部分式となることができ、最も一般的には 条件演算子 内で使用されます:
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 { // A 基底部分オブジェクトを作成 // B の a1 メンバーを作成 // B の a2 メンバーの作成に失敗 // アンワインドにより B の a1 メンバーを破棄 // アンワインドにより A 基底部分オブジェクトを破棄 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" は実際には const char * ではなく 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まで) |