2010-10-22

nested_exceptionの使い方

nested_exceptionは、例外をネストするための、簡単なクラスである。ユーザー定義のクラスから継承して使うことを想定されている。

struct CustomException : std::nested_exception
{ } ;

int main()
{
    try {
        try {
            try {
                throw 123 ; // 何らかのエラー
            } catch( int e )
            { // ここで、元の例外の値を保持しつつ、別の例外を一時的にthrowしたい
                std::cout << e << std::endl ;
                throw CustomException() ;
            }
        } catch( CustomException e ) 
        {
            std::cout << "CustomException" << std::endl ;
            std::rethrow_if_nested( e ) ; // 例外を処理し終わったので、元の例外をthrowする
        }
    } catch( int e )
    { // 元の例外が、さらに外側のハンドラへ届く
        std::cout << e << std::endl ;
    }
}

では、nested_exceptionの実装どうなっているのか。実は、特別なことはなにもしていない。以下のような実装になる。

namespace std {

class nested_exception
{
private :
    exception_ptr ptr ;
public:
    nested_exception() noexcept
        : ptr( current_exception() ; )
    { }

    nested_exception(const nested_exception&) noexcept = default ;
    nested_exception& operator=(const nested_exception&) noexcept = default ;
    virtual ~nested_exception() = default ;
    // access functions
    [[noreturn]] void rethrow_nested [[noreturn]] () const
    {
        if ( ptr == nullptr )
            terminate() ;
        else
             rethrow_exception( ptr ) ;
    }
    exception_ptr nested_ptr() const
    {
        return ptr ;
    }
} ;

} // namespace std

これは単に、コンストラクターでcurrent_exceptionを呼び出しているだけである。つまり、nested_exceptionを継承しなくても、自前のクラスに同じ機能を組み込むことも可能である。しかし、このコードは、誰が書いても同じようになる。わざわざ自前で書かなくてもいいように、nested_exceptionがある。

しかし、わざわざnested_exceptionを継承するのすら面倒ではないか。そう思った君は、一流のC++プログラマーの素質がある。プログラマーは、怠惰であればあるほど、優れているのだ。第一、自分でコードを書くのは、バグのもとではないか。そういうズボラな一流プログラマーのために、throw_with_nestedという関数が用意されている。これは、実引数のクラスが、nested_exceptionを継承していれば、そのままthrowし、そうでない場合は、自動的にnested_exceptionと、実引数のクラスとを継承した、何らかの型を生成してthrowするのだ。

// 俺様お手製の超スゲー例外クラス
struct My_Ultra_Super_Deluxe_Exception
{
} ;

void f()
{
    try {
        // 例外を投げるかもしれない処理
    } catch( ... )
    {
        std::throw_with_nested( My_Ultra_Super_Deluxe_Exception() ) ;
    }
}

このようにすれば、例えば、以下のように書けるわけだ。

void g()
{
    try {
        f() ;
    } catch( My_Ultra_Super_Deluxe_Exception & e )
    {
        // My_Ultra_Super_Deluxe_Exceptionに対する例外処理
        // ネストしている場合、元の例外を再throwする
        std::rethrow_if_nested( e ) ;
    }
}

このようにすれば、元の例外を保持しつつ、別の例外を投げることができる。ハンドラ側では、ある例外に対する処理を行い、もしネストされていれば、再び例外をthrowすることができる。

throw_with_nestedの実装例は、本の虫: pre-Batavia mailingの簡易レビューを参照。throw_if_nestedも、ほぼ似たようなメタプログラムで実現できる。

もちろん、これらの関数は、すべて自前で実装可能である。ただし、一流のメタプログラマーではない限り、このようなメタプログラミングをするのは難しいので、標準ライブラリが用意されているのだ。

No comments: