問題を説明するための適切な質問のタイトルを考えることはできません。うまくいけば、以下の詳細が私の問題を明確に説明しています。
次のコードを検討してください
#include <iostream>
template <typename Derived>
class Base
{
public :
void call ()
{
static_cast<Derived *>(this)->call_impl();
}
};
class D1 : public Base<D1>
{
public :
void call_impl ()
{
data_ = 100;
std::cout << data_ << std::endl;
}
private :
int data_;
};
class D2 : public Base<D1> // This is wrong by intension
{
public :
void call_impl ()
{
std::cout << data_ << std::endl;
}
private :
int data_;
};
int main ()
{
D2 d2;
d2.call_impl();
d2.call();
d2.call_impl();
}
D2
の定義は意図的に間違っていますが、コンパイルして実行します。最初の呼び出しは、初期化されなかっd2.call_impl()
たと予想されるいくつかのランダムビットを出力します。D2::data_
2番目と3番目の呼び出しはすべて、に対して出力100
されますdata_
。
なぜコンパイルされて実行されるのか理解しています。間違っている場合は修正してください。
呼び出しを行うとd2.call()
、呼び出しはに解決されBase<D1>::call
、にキャストさthis
れD1
て呼び出されますD1::call_impl
。D1
は確かに派生形式であるためBase<D1>
、コンパイル時のキャストは問題ありません。
実行時、キャスト後、、this
は、実際にはD2
オブジェクトであるかのように扱われD1
、への呼び出しは、D1::call_impl
あるはずのメモリビットを変更してD1::data_
出力します。この場合、これらのビットはたまたまどこにありますかD2::data_
。d2.call_impl()
2つ目も、C++の実装によっては未定義の動作になると思います。
重要なのは、このコードは意図的に間違っていますが、ユーザーにエラーの兆候を与えないということです。私のプロジェクトで実際に行っているのは、ディスパッチエンジンのように機能するCRTP基本クラスがあることです。call
ライブラリ内の別のクラスは、たとえば、CRTP基本クラスのインターフェイスにアクセスし、基本クラスのデフォルト実装または派生クラスの実装call
にディスパッチします。call_dispatch
ユーザー定義の派生クラス、たとえばD
、が実際にから派生している場合、これらはすべて正常に機能しBase<D>
ます。から派生していないBase<Unrelated>
場所から派生している場合は、コンパイル時エラーが発生します。ただし、ユーザーが上記のようなコードを作成することを妨げることはありません。Unrelated
Base<Unrelated>
ユーザーは、基本CRTPクラスから派生し、いくつかの実装の詳細を提供することにより、ライブラリーを使用します。上記のような誤った使用の問題を回避できる他の設計の選択肢が確かにあります(たとえば、抽象基本クラス)。しかし、とりあえずそれらを脇に置いて、何らかの理由でこのデザインが必要だと私に信じさせましょう。
だから私の質問は、上記のようにユーザーが間違った派生クラスを書くのを防ぐことができる方法はありますか?つまり、ユーザーが派生実装クラス、たとえばを作成D
したが、それをから派生したBase<OtherD>
場合、コンパイル時エラーが発生します。
1つの解決策は使用dynamic_cast
です。ただし、これは広範囲にわたるものであり、機能する場合でも実行時エラーになります。