C言語のマクロでC++の例外処理機構もどきを作る

※開発環境は、Windows2000sp4/IE6sp1/VC++6.0sp6/ConsoleApp

ある条件に当てはまる時、処理を中断したい場合があります。そんなとき、C++では例外処理機構により、スマートに処理できます。

C++言語の例外処理

まず初めにC++の例外処理機構の例を見てみます。

コード1:C++

    #include <cstdio>

    // ここでは MyException クラスの実装は記載しません。

    int main(void)
    {
        FILE *fp1 = NULL;
        FILE *fp2 = NULL;
        try{
            fp1 = fopen("sample1.txt", "r");
            if( fp1 == NULL ){
                throw new MyException;
            }
            fp2 = fopen("sample2.txt", "r");
            if( fp2 == NULL ){
                throw new MyException;
            }
            puts("OK");
        }
        catch(MyException* e){
            puts("error");
            delete e;
        }
        finally{
            if( fp1 != NULL ){
                fclose(fp1);
            }
            if( fp2 != NULL ){
                fclose(fp2);
            }
            puts("last");
        }
        return 0;
    }

  1. try ブロックで、例外が起きそうな処理を行います。
  2. catch ブロックで、直前の try で発生した例外を補足します。例外が起こらなかった場合は無視されます。
  3. finnaly ブロックでは try で成功した場合の後と、catch 処理後の両パターンとも処理されます。

finnaly が使用できない環境もありますが、ここでは対比のため、あえて使用しています。
Java(try-catch-finally)Ruby(begin-rescue-ensure-end)などといった多くの処理系では、finallyの機能が使えますが、 「デストラクタ使って RAII(Resource Acquisition Is Initialization) パターンの C++ だと finally がない」とのことです。(Banさん、ご指摘感謝)

C言語の例外処理「もどき」(1)

ところが、C言語にはそんな便利な機構は存在しません。そこで、マクロを使って簡単な「もどき」を作ってみます。

コード2:マクロ版その1

    #include <stdio.h>

    #define TRY       /* TRY-CATCH */
    #define THROW     goto TRY_TAG_ERROR
    #define CATCH     goto TRY_TAG_RESUME; TRY_TAG_ERROR:
    #define FINALLY   TRY_TAG_RESUME:

    int main(void)
    {
        FILE *fp1 = NULL;
        FILE *fp2 = NULL;
        TRY{
            fp1 = fopen("sample1.txt", "r");
            if( fp1 == NULL ){
                THROW;
            }
            fp2 = fopen("sample2.txt", "r");
            if( fp2 == NULL ){
                THROW;
            }
            puts("OK");
        }
        CATCH{
            puts("error");
        }
        FINALLY{
            if( fp1 != NULL ){
                fclose(fp1);
            }
            if( fp2 != NULL ){
                fclose(fp2);
            }
            puts("last");
        }
        return 0;
    }

ほぼ同じようにできます。このマクロを展開すると、下記のようなコードになります。

コード3:マクロ版その1(マクロ展開後)

    #include <stdio.h>

    int main(void)
    {
        FILE *fp1 = NULL;
        FILE *fp2 = NULL;
        {
            fp1 = fopen("sample1.txt", "r");
            if( fp1 == NULL ){
                goto TRY_TAG_ERROR;
            }
            fp2 = fopen("sample2.txt", "r");
            if( fp2 == NULL ){
                goto TRY_TAG_ERROR;
            }
            puts("OK");
        }
        goto TRY_TAG_RESUME;

    TRY_TAG_ERROR:
        {
            puts("error");
        }

    TRY_TAG_RESUME:
        {
            if( fp1 != NULL ){
                fclose(fp1);
            }
            if( fp2 != NULL ){
                fclose(fp2);
            }
            puts("last");
        }
        return 0;
    }

そう、実態は goto 文です。

C言語の例外処理「もどき」(2)

でも、このままでは、ラベル名が衝突してしまうので、ひとつの関数内に一回しか書けませんし、例外も一種類しか処理できません。
そこで、例外処理を名前で切り分けるようにしたいと思います。

コード4:マクロ版その2

    #include <stdio.h>

    #define TRY                 /* TRY-CATCH */
    #define THROW(name)         goto TRY_TAG_ERROR_##name
    #define CATCH(name, last)   goto TRY_TAG_RESUME_##last; TRY_TAG_ERROR_##name:
    #define FINALLY(last)       TRY_TAG_RESUME_##last:

    int main(void)
    {
        int a = 10;
        FILE *fp1 = NULL;
        FILE *fp2 = NULL;
        TRY{
            fp1 = fopen("sample1.txt", "r");
            if( fp1 == NULL ){
                THROW(A);
            }
            fp2 = fopen("sample2.txt", "r");
            if( fp2 == NULL ){
                THROW(B);
            }
            puts("OK");
        }
        CATCH(A, F){
            puts("error1");
        }
        CATCH(B, F){
            puts("error2");
        }
        FINALLY(F){
            if( fp1 != NULL ){
                fclose(fp1);
            }
            if( fp2 != NULL ){
                fclose(fp2);
            }
            puts("last");
        }
    
        TRY{
            if( a == 10 ){
                THROW(C);
            }
        }
        CATCH(C, L){
            puts("error-c");
        }
        FINALLY(L)
            puts("last-l");
        }
        return 0;
    }

これも展開すると次のようになります。

コード5:マクロ版その2(マクロ展開後)

    #include <stdio.h>

    int main(void)
    {
        int a = 10;
        FILE *fp1 = NULL;
        FILE *fp2 = NULL;
        {
            fp1 = fopen("sample1.txt", "r");
            if( fp1 == NULL ){
                goto TRY_TAG_ERROR_A;
            }
            fp2 = fopen("sample2.txt", "r");
            if( fp2 == NULL ){
                goto TRY_TAG_ERROR_B;
            }
            puts("OK");
        }
        goto TRY_TAG_RESUME_F;
    
    TRY_TAG_ERROR_A:
        {
            puts("error1");
        }
        goto TRY_TAG_RESUME_F;
    
    TRY_TAG_ERROR_B:
        {
            puts("error2");
        }
    
    TRY_TAG_RESUME_F:
        {
            if( fp1 != NULL ){
                fclose(fp1);
            }
            if( fp2 != NULL ){
                fclose(fp2);
            }
            puts("last");
        }
    
        {
            if( a == 10 ){
                goto TRY_TAG_ERROR_C;
            }
        }
        goto TRY_TAG_RESUME_L;
    
    TRY_TAG_ERROR_C:
        {
            puts("error-c");
        }
    
    TRY_TAG_RESUME_L:
        {
            puts("last-l");
        }
        return 0;
    }

あとがき

goto 文を使うことの良し悪しについての議論もありますが、こういった例外処理のために使うのであれば問題ないと思います。
もっとも、ここでその話題について論議するつもりはありません。

また、C++言語の例外処理機構をよくご存知の方は、色々と文句をつけたくなるかもしれませんが、その場合は、ぜひ、ご自分で対応なされたコードを公開し、告知してください。皆に喜ばれるかもしれません。


[HOME]-[RTL]
Copyright© 2005 RAPT.