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も、ほぼ似たようなメタプログラムで実現できる。

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

1 comment:

Anonymous said...

実装例でサボるのはいいですけど、My_Ultra_Super_Deluxe_Exception がポリモーフィックでないといけないという制限は書いておいた方がいいと思いました