C++ における例外処理の標準構文
C++ における標準的な例外処理 への移動
例外処理では、try
、throw
、catch
の 3 つのキーワードを使用する必要があります。throw
キーワードは、例外を発生させるのに使用されます。try ブロックには、例外を送出する可能性のあるステートメントが含まれ、そのブロックの後には 1 つ以上の catch 文が続きます。それぞれの catch 文では特定の例外型を処理します。
メモ:
try
、catch
、throw
の各キーワードは C プログラムでは使用できません。
try ブロック
try ブロックには、例外を送出する可能性のある 1 つ以上のステートメントが含まれています。プログラムでは、throw 文を実行することで例外を送出します。throw 文は一般に関数内で実行されます。以下に例を示します。
void SetFieldValue(DF *dataField, int userValue)
{
if ((userValue < 0) || userValue > 10)
throw EIntegerRange(0, 10, userValue);
// ...
}
プログラムのもう 1 つの部分では、送出された例外オブジェクトを捕捉して適宜処理できます。以下に例を示します。
try {
SetFieldValue(dataField, userValue);
}
catch (EIntegerRange &rangeErr) {
printf("Expected value between %d and %d, but got %d\n", rangeErr.min,
rangeErr.max, rangeErr.value);
}
上記の例で、関数 SetFieldValue は、入力パラメータが無効であることがわかった場合、例外を送出してそれを示すことができます。例外が送出された場合は、printf 文が実行されます。例外が送出されない場合、printf 文は実行されません。
try
で指定された try ブロックの直後には、catch
で指定されたハンドラが記述される必要があります。try ブロックは、プログラムの実行時の制御フローを指定するステートメントです。try ブロックで例外が送出された場合、プログラムの制御は適切な例外ハンドラに委ねられます。
ハンドラは、例外の処理を目的としたコード ブロックです。C++ 言語では、try ブロックの直後にハンドラが少なくとも 1 つ必要です。プログラムでは、その中で発生し得る例外ごとにハンドラを用意しなければなりません。
throw 文
throw 文では、さまざまな型のオブジェクトを送出できます。C++ のオブジェクトは一般に、値、参照、ポインタのいずれでも送出できます。以下に例を示します。
// throw an object to be caught by value or reference
throw EIntegerRange(0, 10, userValue);
// throw an object to be caught by pointer
throw new EIntegerRange(0, 10, userValue);
メモ: 例外を値で送出し参照で捕捉すると、メモリ リークを防ぐことができます。例外をポインタで捕捉した場合は、その例外オブジェクトを削除できないおそれがあります。
メモ: 例外を値で送出するには、その例外に public なコピー コンストラクタと public なデストラクタが必要です。
さらに、throw
文では、整数やポインタなどのプリミティブ型も送出できます。次の 2 つの例では、主として標準の完全性を保つために用意されている機能を示しています。もっと状況がわかりやすい例外を送出する方がよいでしょう。整数などの組み込み型を送出するのは特別な場合です。また、例外をポインタで送出しない方がよいでしょう。
// throw an integer
throw 1;
// throw a char *
throw "foo";
プリミティブ値ではなく例外オブジェクトを使用する方が効率的なことがありますが、それは以下の理由からです。
- 例外の意味がわかりやすくなる可能性がある。
- クラス多態性を利用して例外をフィルタリングできる。
例外を参照で、特に const
参照で捕捉する場合がほとんどです。値での例外オブジェクトの捕捉に注意が必要な場合があります。値で捕捉されるオブジェクトは、コピーされてから catch のパラメータに割り当てられる必要があります。ユーザーがコピー コンストラクタを用意した場合は、それが呼び出され、その結果、効率がさらに低下するおそれがあります。
catch 文
catch 文にはいくつかの形式があります。オブジェクトは、値、参照、ポインタのいずれでも捕捉できます。さらに、catch のパラメータに const 修飾子を適用することもできます。単一の try ブロックに複数の catch 文を用意して、さまざまな種類の例外を 1 つのブロックで捕捉することができますが、その場合は、送出される可能性のある例外ごとに catch 文が必要です。以下に例を示します。
try
CommitChange(dataBase, recordMods);
catch (const EIntegerRange &rangeErr)
printf("Got an integer range exception");
catch (const EFileError &fileErr)
printf("Got a file I/O error");
CommitChange 関数で複数のサブシステムが使用されており、それらのサブシステムがさまざまな型の例外を送出する可能性がある場合は、それぞれの例外型を別々に処理するのがよいでしょう。単一の try 文に複数の catch 文がある場合は、例外の型ごとにハンドラを用意できます。
例外オブジェクトが何らかの基底クラスから派生される場合は、派生した例外クラスに特化したハンドラを追加するほか、基底クラスにジェネリック ハンドラを用意するのがよいでしょう。それには、例外送出時の希望する検索順に catch 文を並べます。たとえば、以下のコードでは、EIntegerRange をまず処理し、次に ERange(EIntegerRange の派生元)を処理します。
try
SetFieldValue(dataField, userValue);
catch (const EIntegerRange &rangeErr)
printf("Got an integer range exception");
catch (const ERange &rangeErr)
printf("Got a range exception");
最後に、try ブロックで送出される可能性のある例外をすべて当該ハンドラで捕捉させる場合は、catch(...)
という特殊な形式を使用します。これは、あらゆる例外に対して当該ハンドラを呼び出すように例外処理システムに指示するものです。以下に例を示します。
try
SetFieldValue(dataField, userValue);
catch (...)
printf("Got an exception of some kind");