記事検索
月別アーカイブ
アクセスカウンター

    タグ絞り込み検索
    2010年06月16日13:23C/C++のエラー処理パターン
    Cだとgotoをつかうのが基本。C++だとtry/throw/catchもあり。 めんどくさいのが、atomicオペレーションを実装するときに、内部で副作用があちこちに発生して 途中で失敗したら取消てまわらないといけない場合。 正常系はこんなかんじだとして
    int
    main(int ac, char **av)
    {
        int rc = 1;
        int x = av[1] ? atoi(av[1]) : 0; /*atoiのエラーはみぬふり*/
        f(x); /*エラーをおこすかも*/
        g(x); /*エラーをおこすかも*/
        return 0;
    }
    

    Cのgotoパターン。
    #include <iostream>
    #include <cstdlib>
    
    using std::cout;
    using std::endl;
    
    int a = 0;
    int b = 0;
    void
    show()
    {
        cout << "a=" << a << endl
             << "b=" << b << endl;
    }
    
    int
    f(int x)
    {
        if (x > 100)
            return -1;
        a += x;
        return 0;
    }
    void
    undo_f(int x)
    {
        a -= x;
    }
    
    int
    g(int x)
    {
        if (x > 10)
            return -1;
        b += x;
        return 0;
    }
    
    int
    main(int ac, char **av)
    {
        int rc = 1;
        int x = av[1] ? atoi(av[1]) : 0;
        if (f(x) < 0)
            goto err_f;
        if (g(x) < 0)
            goto err_g;
        rc = 0;
        goto out;
    
     err_g:
        undo_f(x);
     err_f:
     out:
        show();
        cout << "rc=" << rc << endl;
        return rc;
    }
    
    g(x)が失敗したf(x)を取り消すためにundo_f(x)を呼び出す必要がある。 副作用をおこすブロック(この例だとf()とかg())毎に対応する gotoラベルを用意してリカバリルーチンを逆順に呼んでいく。 問題はf(),g()の部分とリカバリの部分がコード上で離れてしまうのと 逆順に書くの人間がやらないといけないところ。f()とg()の順序が逆になったときや f()とg()の間にh()が入ったときにリカバリ部分も書き換えないといけない。 たぶん書き換えるのは忘れないとおもうが、実装ミスをする個所が増える。
    C++の例外処理をつかった例
    #include <iostream>
    #include <cstdlib>
    
    using std::cout;
    using std::endl;
    
    int a = 0;
    int b = 0;
    void
    show()
    {
        cout << "a=" << a << endl
             << "b=" << b << endl;
    }
    
    struct ERR {};
    
    void
    f(int x)
    {
        if (x > 100)
            throw ERR();
        a += x;
    }
    void
    undo_f(int x)
    {
        a -= x;
    }
    
    void
    g(int x)
    {
        if (x > 10)
            throw ERR();
        b += x;
    }
    
    int
    main(int ac, char **av)
    {
        int rc = 1;
        int x = av[1] ? atoi(av[1]) : 0;
        try {
            f(x);
            try {
                g(x);
                rc = 0;
            }
            catch (ERR&) {
                undo_f(x);
            }
        }
        catch (ERR&) {
        }
        show();
        cout << "rc=" << rc << endl;
        return rc;
    }
    
    tryブロックの入れ子で実装している。gotoパターンに比べて、正常系の処理とエラーリカバリの処理とが 構文上のブロックとして関連づけられるのでミスが減るが、外のtryブロックほどcatchブロックが離れてしまうので コード上の見通しがわるくなる。
    おなじく例外処理をつかう別の例
    #include <iostream>
    #include <stdexcept>
    #include <cstdlib>
    
    using std::cout;
    using std::endl;
    using std::exception;
    using std::runtime_error;
    
    int a = 0;
    int b = 0;
    void
    show()
    {
        cout << "a=" << a << endl
             << "b=" << b << endl;
    }
    
    struct ERR {};
    
    void
    f(int x)
    {
        if (x > 100)
            throw ERR();
        a += x;
    }
    void
    undo_f(int x)
    {
        a -= x;
    }
    
    void
    g(int x)
    {
        if (x > 10)
            throw ERR();
        b += x;
    }
    
    void
    proc2(int x)
    {
        try {
            g(x);
        }
        catch (ERR&) {
            throw;
        }
    }
    
    void
    proc1(int x)
    {
        f(x);
        try {
            proc2(x);
        }
        catch (ERR&) {
            undo_f(x);
            throw;
        }
    }
    
    int
    main(int ac, char **av)
    {
        int rc = 1;
        int x = av[1] ? atoi(av[1]) : 0;
        try {
            proc1(x);
            rc = 0;
        }
        catch (ERR&) {
        }
        show();
        cout << "rc=" << rc << endl;
        return rc;
    }
    
    1関数1処理に限定するように書くことで構文上のtryの入れ子を避けている。 呼び出し関係から実際にはtryは入れ子になっている。
    C++0xで入るlambdaをつかった例
    #include <iostream>
    #include <stdexcept>
    #include <cstdlib>
    
    using std::cout;
    using std::endl;
    using std::exception;
    using std::runtime_error;
    
    int a = 0;
    int b = 0;
    void
    show()
    {
        cout << "a=" << a << endl
             << "b=" << b << endl;
    }
    
    struct ERR {};
    
    void
    f(int x)
    {
        if (x > 100)
            throw ERR();
        a += x;
    }
    void
    undo_f(int x)
    {
        a -= x;
    }
    
    void
    g(int x)
    {
        if (x > 10)
            throw ERR();
        b += x;
    }
    
    template <typename F>
    struct cmd
    {
        F* pf;
        cmd(F& f) : pf(&f) {}
        ~cmd() { if (pf) (*pf)(); }
        void clear() { pf = 0; }
    };
    template <typename F>
    cmd<F>
    make_cmd(F f)
    {
        return cmd<F>(f);
    }
    
    int
    main(int ac, char **av)
    {
        int rc = 1;
        int x = av[1] ? atoi(av[1]) : 0;
        try {
            f(x);
            auto uf (make_cmd([&x]() { undo_f(x); }));
            g(x);
            uf.clear(); //gが成功したらfのundoは不要。
            rc = 0;
        }
        catch (ERR&) {
        }
        show();
        cout << "rc=" << rc << endl;
        return rc;
    }
    


    このエントリーをはてなブックマークに追加

    トラックバックURL

    コメントする

    名前
     
      絵文字