にゃははー

はへらー

virtual dtor + exception specification + multiple deriving

最悪だ。

姫始め 違った コトハジメ

例えば次のコードを見て欲しい

struct B1
{
	~B1() throw(X);
};
struct B2
{
	~B2(); // with noexcept(true) in C++11, noexcept(false) in C++03 implicitry
};

struct D : B1, B2
{
	// ~D();
};

さて、この時コンパイラが暗黙的に生成するD::~D()の例外指定はどうなってるでしょうか。
C++11だとthrow(X),C++03だとnoexcept(false)相当のはずです。

で?どうしたって?

しかし基底クラスがvirtual dtorを持ってた場合はどうなるでしょうか。結論から言うとill-formedとなります。つまりエラー。
C++11ではB2::~B2()にvirtualを付けるとエラー、C++03ではB1::~B1()にvirtualを付けるとエラーになります。

どうしてこんなことになるかというと、virtualの付いた関数をoverrideするとき、派生側では基底の指定しているexception-specと同等かより厳しいexception-specを指定する必要があります。
つまり派生するに従って投げうる例外の型は単調減少しなければなりません。

一方exception-specの付いた複数の基底クラスから派生したspecifal member functionsというのは、暗黙的に全ての基底が投げうる例外を許容するような指定になります。
つまり基底クラスが増えればそれに従って投げうる例外の型は単調増加します。

special member functionsのうちvirtualになりうるのはdtorとassign operatorだけですが、assign operatorなぞvirtualにする機会に遭遇するとは思えないです。
そうするとvirtual dtor + exception-spec + multiple derivingは最悪の組み合わせとなるのです。

これの解決方法は派生側で明示的にnoexcept/noexcept(true)/throw()なdtorを与えれば良いことになります。

ちょっとまった。

実はここにも嵌りポイントが。
もしD::~D() noexceptと指定して、さらになんでそんな実装なのかちょっと私には理解できなくて実装したそいつの頭かちわっt... ...として、B1::~B1()がXのインスタンスを投げやがったらどうなるでしょうか。
virtual dtorでD::~D()に処理が委譲され、D::~D()の本体が実行されたあとにD::~D()はB1::~B1()/B2::~B2()を呼びに行きます。
そうするとB1::~B1()がXを投げつけてくるわけですが、D::~D()は華麗にそいつを捕まえてもれなくstd::terminateに処理を委譲します。

基底の型が分かってるんだからD::~D() throw(X)って書けと?あなた基底クラスがtemplate argumentで渡されたらどうするんですか...
ちょっと人間様にはどうにもできないです...

オウフ...

そしてまさにそれにぶつかってるのがlibstdc++のstd::nested_exception。std::nested_exception::~nested_exception()自体はexception-specがないので暗黙的にnoexceptになります。(肝心のGCCはまだ実装してないっぽい?
また、libstdc++のstd::throw_with_nestedが投げるunspecified typeはstd::_Nested_exceptionですが、std::_Nested_exception::~_Nested_exception()はコンパイラの暗黙生成に委ねられています。
さてここでstd::runtime_errorを投げようと画策しましょう。そうするとstd::runtime_error::~runtime_error()はuser-proviced dtorなので暗黙的にnoexceptです。(現在のlibstdc++はthrow()が明示的に与えられています。

なんとなく分かってきたでしょうか。つまり現状、GCCでは

std::throw_with_nested( std::runtime_error( "what" ) );

ill-formedなんだよ!!!!Ω<ナ、ナンダッテー

バグレポは出してないですがstd::_Nested_exceptionにnoexcept指定すればとりあえず逃れられますし、そもそも例外クラスのdtorが例外投げるとかやめてくださいって話なのでまぁここはいいのです。

が、問題なのはvirtual dtor + exception-spec + multiple derivingで王手車角金銀桂香取りだったのにもかかわらずtemplateが入ることでチェックメイトになってしまうことです。(ゲームが違ってしまうぐらいヤバイ

結論

として私が言いたいことというのが、
もうdtorのexception-specはdeprecatedにして、既存コードでは該当箇所を無視し常にnoexceptにしましょうということが言いたい。
dtorが例外投げるって相当やばいですよ?double throwがやばいとかが言いたいんではなくて、クリーンアップ処理でどうしようもなくなって例外投げるぐらいのどうしようも無さなのに、人間ごときがそれをどうこうできるわけないですよってこと。
close(2)がエラー返したらもう潔く死ぬしか無いですよね...