2011-08-11

例外オブジェクトとしてのクラス

追記。正しくは、例外オブジェクトは、コピーかムーブのどちらかをサポートしていなければならないというものであった。つまり、コピーするのならば、ムーブは必要ないし、ムーブしかできないクラスも、例外オブジェクトたりえる。したがって、この記事の内容は誤りである。

例外として投げるオブジェクトのことを、例外オブジェクトという。例外オブジェクトがクラスのオブジェクトである場合、コピーコンストラクター、ムーブコンストラクター、デストラクターにアクセス可能でなければならない。(たとえ、使われなかったとしても)

struct X
{
    X() = default ;
    X( X const & ) = delete ;
} ;

void f()
{
    throw X() ; // エラー、コピーコンストラクターにアクセスできない
}

しかし、これは非常にわかりにくいと思う。例えば、以下のコードは誤りである。

struct X
{
    X() = default ;
    X( X const & ) { } // ユーザー定義コピーコンストラクター
} ;

void f()
{
    throw X() ; // エラー、ムーブコンストラクターにアクセスできない
} 

なぜかというと、ユーザー定義のコピーコンストラクターがある場合、ムーブコンストラクターは暗黙にdefault化されないからだ。よって、エラーになる。どうやら、今のC++コンパイラーは、まだこの挙動を正しく実装していないようだ。

これは、コピーコンストラクターに限った話ではない。ユーザー定義のコピー代入演算子や、ムーブ代入演算子、デストラクターがある場合、ムーブコンストラクターは暗黙にdefault化されない。

また、暗黙のコピーコンストラクターは、ユーザー定義のコピー代入演算子や、ムーブコンストラクター、ムーブ代入演算子、デストラクターがある場合、default化される。しかし、この挙動はdeprecatedである。将来的には、廃止されるだろう。とすると、将来、C++の改定されたときに、以下のコードは動かなくなる可能性がある。

struct X
{
    X() = default ;
    X( X && ) { } // ユーザー定義のムーブコンストラクター
} ;

int main()
{
    throw X() ; // C++0xではOK。ただし、将来的には不安。
}

もちろん、これは例外に限った話ではない。C++0xで、暗黙の特別なメンバー関数が欲しい場合は、明示的にdefault化すべきだろう。

2 comments:

Anonymous said...

> コピーコンストラクター、ムーブコンストラクター、デストラクターに
> アクセス可能でなければならない。

コピーできてムーブできないクラスを例外オブジェクトとして禁じる理由が
思い当たりませんし、これが正しいとすると既存の C++03 向けコードが
エラーになってしまう場面が多く考えられます。

規格の意図としては、ライブラリが要求する MoveConstructible と同様に
rvalue による初期化でムーブかコピーのいずれかが可能であることを要求
したいだけであり、文面が間違っていると考えるのが妥当ではないでしょうか?

江添亮 said...

一応、ムーブコンストラクターが暗黙にも明示的にも宣言されていないときは、ムーブコンストラクターを呼び出す式は、コピーコンストラクターを呼び出すとされています。