この章では、C++ コンパイラの例外処理の実装について説明します。「11.2 マルチスレッドプログラムでの例外の使用」にも補足情報を掲載しています。例外処理の詳細については、『プログラミング言語 C++』(第 3 版、Bjarne Stroustrup 著、アスキー、1997 年) を参照してください。
例外処理では、配列範囲のチェックといった同期例外だけがサポートされます。同期例外とは、例外を throw 文からだけ生成できることを意味します。
C++ 標準でサポートされる同期例外処理は、終了モデルに基づいています。終了とは、いったん例外が送出されると、例外の送出元に制御が二度と戻らないことを意味します。
例外処理では、キーボード割り込みなどの非同期例外の直接処理は行えません。ただし、注意して使用すれば、非同期イベントが発生したときに、例外処理を行わせることができます。たとえば、シグナルに対する例外処理を行うには、大域変数を設定するシグナルハンドラと、この変数の値を定期的にチェックし、値が変化したときに例外を送出するルーチンを作成します。シグナルハンドラから例外をスローすることはできません。
例外に関する実行時エラーメッセージには、次の 5 種類があります。
例外のハンドラがありません
予期しない例外を送出
ハンドラでは例外の再送出しかできません
スタックの巻き戻し中は、デストラクタは独自の例外を処理しなければなりません
メモリー不足
実行時にエラーが検出されると、現在の例外の種類と、前述の 5 つのメッセージのいずれかがエラーメッセージとして表示されます。デフォルト設定では、事前定義済みの terminate() 関数が呼び出され、さらにこの関数から abort() が呼び出されます。
コンパイラは、例外指定に含まれている情報に基づいて、コードの生成を最適化します。たとえば、例外を送出しない関数のテーブルエントリは抑止されます。また、関数の例外指定の実行時チェックは、できるかぎり省略されます。
プログラムで例外を使用しないことが明らかであれば、-features=no%except コンパイラオプションを使用して、例外処理用のコードの生成を抑止することができます。このオプションを使用すると、コードサイズが若干小さくなり、実行速度が多少高速になります。ただし、例外を無効にしてコンパイルしたファイルを、例外を使用するファイルにリンクすると、例外を無効にしてコンパイルしたファイルに含まれている局所オブジェクトが、例外が発生したときに破棄されずに残ってしまう可能性があります。デフォルト設定では、コンパイラは例外処理用のコードを生成します。時間と容量のオーバーヘッドが重要な場合を除いて、通常は例外を有効のままにしておいてください。
C++ 標準ライブラリ、dynamic_cast、デフォルトの new 演算子では例外が必要です。そのため、標準モード (デフォルトモード) でコンパイルを行う場合は、例外を無効にしないでください。
標準ヘッダー <exception> には、C++ 標準で指定されるクラスと例外関連関数が含まれています。このヘッダーは、標準モード (コンパイラのデフォルトモード、すなわち -compat=5 オプションを使用するモード) でコンパイルを行うときだけ使用されます。次は、<exception> ヘッダーファイル宣言を抜粋したものです。
// standard header <exception> namespace std { class exception { exception() throw(); exception(const exception&) throw(); exception& operator=(const exception&) throw(); virtual ~exception() throw(); virtual const char* what() const throw(); }; class bad_exception: public exception {...}; // Unexpected exception handling typedef void (*unexpected_handler)(); unexpected_handler set_unexpected(unexpected_handler) throw(); void unexpected(); // Termination handling typedef void (*terminate_handler)(); terminate_handler set_terminate(terminate_handler) throw(); void terminate(); bool uncaught_exception() throw(); } |
標準クラス exception は、構文要素や C++ 標準ライブラリから送出されるすべての例外のための基底クラスです。exception 型のオブジェクトは、例外を発生させることなく作成、複製、破棄することができます。仮想メンバー関数 what() は、例外についての情報を示す文字列を返します。
C++ release 4.2 で使用される例外との互換性について、ヘッダー <exception.h> は標準モードでの使用のため提供されます。このヘッダーは、C++ 標準のコードに移行するためのもので、C++ 標準には含まれていない宣言を含んでいます。開発スケジュールに余裕があれば、<exception.h> の代わりに <exception> を使用し、コードを C++ 標準に従って更新してください。
// header <exception.h>, used for transition #include <exception> #include <new> using std::exception; using std::bad_exception; using std::set_unexpected; using std::unexpected; using std::set_terminate; using std::terminate; typedef std::exception xmsg; typedef std::bad_exception xunexpected; typedef std::bad_alloc xalloc; |
互換モード (—compat[=4]) では、ヘッダー <exception> は使用できません。また、ヘッダー <exception.h> は、C++ release 4.2 で提供されているヘッダーと同じですので、ここでは再生されません。
同じプログラムの中で、setjmp/longjmp 関数と例外処理を併用することができます。ただし、これらが相互に干渉しないことが条件になります。
その場合、例外と setjmp/longjmp のすべての使用規則が、それぞれ別々に適用されます。また、A 地点から B 地点への longjmp を使用できるのは、例外を A 地点から送出し、B 地点で捕獲した場合と効果が同じになる場合だけです。特に、try ブロック (または catch ブロック) への、または try ブロック (または catch ブロック) からの、直接的または間接的な longjmp や、自動変数や一時変数の初期化や明示的な破棄の前後にまたがる longjmp は行なってはいけません。
シグナルハンドラからは例外を送出できません。
C++ コードを含むプログラムは -Bsymbolic を使用せず、代わりにリンカーマップファイルまたはリンカースコープオプションを使用してください (「4.1 リンカースコープ」 を参照)。-Bsymbolic を使用すると、異なるモジュール内の参照が、本来 1 つの大域オブジェクトの複数の異なる複製に結合されてしまう可能性があります。
例外メカニズムは、アドレスの比較によって機能します。オブジェクトの複製が 2 つある場合は、アドレスが同一であると評価されず、本来一意のアドレスを比較することで機能する例外メカニズムで問題が発生することがあります。