例外処理

スポンサーリンク

例外

プログラム実行時に発生する異常を例外 (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::exceptionexceptionすべての標準例外クラスの基底
std::logic_errorstdexcept論理エラー
std::runtime_errorstdexcept実行時エラー
std::bad_allocnewメモリ確保に失敗
std::bad_casttypeinfoキャストに失敗
std::out_of_rangestdexcept範囲外の添字
std::invalid_argumentstdexcept無効な引数

例外の伝播

次のプログラムは,関数 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 関数を用いて変更可能です。

スポンサーリンク