第45章 えっ!?

 えっ、エラー!? そんなときに例外処理。


 では、今回の要点です。


 では、いってみましょう。


 今回から数章にわたってエラー処理について話そうと思います。エラー処理というと第3部第5章の goto なんかもありますね。しかし、C++にはもっと洗練されたエラー処理の方法が用意されています。

 先ずは、第3部第5章のプログラムをもう一度見てみましょう。

bool Func()
{
    FILE *pf1 = NULL;
    FILE *pf2 = NULL;
    FILE *pf3 = NULL;
    bool bRet = false;

    pf1 = fopen(szFile1, "r");
    if(pf1 == NULL)
        goto ON_ERROR;

    pf2 = fopen(szFile2, "r");
    if(pf2 == NULL)
        goto ON_ERROR;

    pf3 = fopen(szFile3, "w");
    if(pf3 == NULL)
        goto ON_ERROR;

    Func2(pf1, pf2, pf3);
    bRet = true;

ON_ERROR:
    if(pf1 != NULL)
        fclose(pf1);
    if(pf2 != NULL)
        fclose(pf2);
    if(pf3 != NULL)
        fclose(pf3);

    return bRet;
}

 「あれ? 前のと違うんじゃ」と思った方、正解です。もうちょっと綺麗な形に書き直しました。先ず最初に NULL で初期化しておき、エラー時の処理、正常時の処理、関係なく同じコードでファイルを閉じられるようにしてみました。

 これが goto 文でのエラー処理の方法です。fopen の戻り値を確認して、エラーなら最後に飛ぶというものでした。

 これをもうちょっと改造してみましょう。戻り値を true と false だけにするのではなく、どのファイルでエラーが発生したかも分かるようにします。つまり、エラーの発生したときはファイルの番号(1,2,3)を返し、正常終了の時は0を返そうということです。

 どうやるかは簡単ですね。goto の前に bRet のような変数に値を入れればいいだけです。

int Func()
{
    FILE *pf1 = NULL;
    FILE *pf2 = NULL;
    FILE *pf3 = NULL;
    int fRet = 0;

    pf1 = fopen(szFile1, "r");
    if(pf1 == NULL)
    {
        fRet = 1;
        goto ON_ERROR;
    }

    pf2 = fopen(szFile2, "r");
    if(pf2 == NULL)
    {
        fRet = 2;
        goto ON_ERROR;
    }

    pf3 = fopen(szFile3, "w");
    if(pf3 == NULL)
    {
        fRet = 3;
        goto ON_ERROR;
    }

    Func2(pf1, pf2, pf3);
ON_ERROR:
    if(pf1 != NULL)
        fclose(pf1);
    if(pf2 != NULL)
        fclose(pf2);
    if(pf3 != NULL)
        fclose(pf3);

    return fRet;
}

 はい。これでいいですね。でも、何か if 文が大きくふくれて、鬱陶しいですね。


 こういうとき、C++の例外処理を使えばすっきりします。例外処理とはなんなのか...それを話す前に、とりあえずどんなものか見てみましょう。

int Func()
{
    FILE *pf1 = NULL;
    FILE *pf2 = NULL;
    FILE *pf3 = NULL;
    int fRet = 0;

    try
    {
        pf1 = fopen(szFile1, "r");
        if(pf1 == NULL)
            throw 1;

        pf2 = fopen(szFile2, "r");
        if(pf2 == NULL)
            throw 2;

        pf3 = fopen(szFile3, "w");
        if(pf3 == NULL)
            throw 3;

        Func2(pf1, pf2, pf3);
    }
    catch(int fError)
    {
        fRet = fError;
    }

    if(pf1 != NULL)
        fclose(pf1);
    if(pf2 != NULL)
        fclose(pf2);
    if(pf3 != NULL)
        fclose(pf3);

    return fRet;
}

 赤くなったところに注目して下さい。今までに見たことのない try, catch, throw という命令が使われています。これらが例外処理用の命令です。

 先ずは try があります。その次に { } があって、その中に処理が書かれています。何か if 文みたいですが、特に条件のようなものは見あたりません。try は「試しにやってみる」という意味ですが、どういうことなのでしょうね。

 次に、エラーが発生したときに throw という命令を使っています。ここには、前のプログラムでは goto 文があった場所ですね。なので、どこかに処理が移動しそうです。そして、throw は return 文のように値を1つ引き連れています。ここの値は前に fRet に代入した値と同じです。いったいどういうことなのでしょうね。

 で、Func2 の次に catch という命令を使っています。その次のカッコ内では fError という変数が宣言されています。そして、fRet にその値を代入しています。fError の初期化はどうしたのでしょうか?

 最後にはいつも通りのファイルを閉じるコードが書かれています。さぁ、どんな動作をするのか予想はつきましたか?


 それでは答です。

 throw 文は「例外」を発生させるための命令です。この「例外」というものが try ブロック内で発生すると、catch ブロックに飛びます。try ブロックとは try の後に書かれた { } 内のことで、catch ブロックとは同じく catch の後に書かれた { } 内のことです。「try」して(やってみて)、エラーがあれば「throw」して(投げて)、その例外を「catch」する(取る)わけです。

 このとき、fError は throw で投げた値で初期化されます。なので、fRet には throw で投げた値が代入されることになります。

 そして、例外が発生しなかった場合は catch ブロックは無視されます。決して fError = 0 で実行されるということはありません。正常な場合は try ブロックのみが実行され、catch ブロックは例外が発生したときだけ実行されるわけです。

 これらのことを踏まえてもう一度プログラムをじっくり見てみて下さい。例外処理がどんなものか分かってくると思います。いろいろ改造して楽しんでみて下さい。


 では、今回の要点です。


 まだ goto を使ったものと比べて大幅にコードが減っているようには見えませんが、これはまだ例外処理の実力の半分しか使っていないからです。その実力は、次回からじっくり見ていきましょう。

 それでは。


第44章 テンポラリ | 第46章 えっ!? 2

Last update was done on 2001.2.25

この講座の著作権はロベールが保有しています