例外処理
例外
プログラム実行時に発生する異常を例外 (exception) と呼びます。
例外処理機構を利用することで,問題の検出と解決を分離して記述できます。
例外の検出と解決は,次の try-catch 文を用いて記述します。
try 節には,例外が発生しうる処理,catch 節には,例外が発生した場合に行う処理を記述します。
try {
// 例外が発生しうる処理
}
catch (例外型 引数) { // 引数は省略可
// 例外が発生した場合に行う処理
}
try 節の中で例外が発生すると,処理は直ちに対応する catch 節へ移ります。
catch 節は複数書くことができますが,実行されるのは例外型がマッチした最初の節だけです。
派生クラス型の例外は,その基底クラス型の catch 節にもマッチします。
例外型と引数の代わりに ... と書くと,その catch 節は例外型を問いません。
次のプログラムは,some_exception 型の例外を扱うプログラムの例です。
#include <iostream>
// 例外を表すクラス
class some_exception
{
private:
const char* msg; // 例外を説明するメッセージ
public:
some_exception(const char* msg) : msg(msg) { } // コンストラクタ
const char* what() { return msg; } // メッセージを返す
};
int main()
{
try
{
throw some_exception("some error message"); // 例外をスロー
}
catch (some_exception e) // some_exception 型の例外をキャッチ
{
std::cerr << "some_exception: " << e.what() << std::endl;
}
catch (...) // その他の例外をキャッチ
{
std::cerr << "unknown exeption" << std::endl;
}
return 0;
}
<b>[出力]</b>
some_exception: some error message
例外の発生は,次の throw 式を用いて書きます。
throw 式に与えたオブジェクトは例外オブジェクトと呼ばれ,catch 節で使用されます。
throw 例外オブジェクト
throw 式で例外を発生させることを,例外のスロー (throw, 投下) といいます。
catch 節で例外を捕えることを,例外のキャッチ (catch, 捕捉) といいます。
例外クラス
標準で用意されている代表的な例外クラスには,次のようなものがあります。
例外クラス | ヘッダ | 説明 |
---|---|---|
std::exception | exception | すべての標準例外クラスの基底 |
std::logic_error | stdexcept | 論理エラー |
std::runtime_error | stdexcept | 実行時エラー |
std::bad_alloc | new | メモリ確保に失敗 |
std::bad_cast | typeinfo | キャストに失敗 |
std::out_of_range | stdexcept | 範囲外の添字 |
std::invalid_argument | stdexcept | 無効な引数 |
例外の伝播
次のプログラムは,関数 func2 でスローされた例外を main でキャッチするものです。
#include <iostream>
#include <stdexcept>
void func2()
{
throw std::runtime_error("some error message");
}
void func1()
{
func2(); // 例外はキャッチされない
}
int main()
{
try {
func1();
} catch (std::runtime_error e) {
std::cerr << "runtime_error: " << e.what() << std::endl;
}
return 0;
}
<b>[出力]</b>
runtime_error: some error message
例外はキャッチされない限り,関数の呼び出しを次々と辿ります。
最終的に main 関数でもキャッチされなかった場合,標準関数 std::terminate が呼び出されます。
func1 において例外を一旦キャッチした後,再びスローするには,次のようにします。
例外のリスローは throw; とだけ書きます。
void func1()
{
try {
func2();
} catch (std::runtime_error) {
throw; // 例外をリスロー
}
}
例外仕様
関数の投げる例外の型が限定される場合,それらを列挙した例外仕様を書くことができます。
例外仕様は,引数リストの後に throw(例外型1, 例外型2, ...) と書きます (関数プロトタイプも同様)。
#include <iostream>
#include <stdexcept>
void func1()
throw(std::logic_error, std::runtime_erorr); // 例外仕様
void func1()
throw(std::logic_error, std::runtime_erorr) // 例外仕様
{
throw std::runtime_error("some error message");
}
int main()
{
try {
func1();
} catch (std::logic_error e) {
std::cerr << "logic_error: " << e.what() << std::endl;
} catch (std::runtime_error e) {
std::cerr << "runtime_error: " << e.what() << std::endl;
}
return 0;
}
関数が例外を投げない場合,例外仕様は単に throw() と書きます。
void func1() throw() { ... }
例外仕様にない型の例外がスローされた場合,std::unexpected 関数が呼び出されます。
デフォルトでは,std::unexpected 関数は std::terminate 関数を呼び出しますが,この振る舞いは set::unexpected 関数を用いて変更可能です。