基本クラスの専門化

いくつかの基本クラス、相続人、および相続人インスタンスでいくつかのアクションを実行するテンプレートハンドラがいくつかあります。 その動作は、処理中のクラスのベースとなるクラスによって異なります。 可能なオプションを示したい。



いくつかの基本クラスと、それらから継承できるクラスがあるとします。

したがって、Base1、Base2、Base3、およびクラスDerived12、Derived23があります。



class Derived12: public Base1, public Base2 {}; class Derived23: public Base2, public Base3 {};
      
      





そして、いくつかのクラスのExecutorがあります。



 template<typename T> struct Executor { void operator()(const T&); };
      
      





この例では、関数引数を使用せず、operator()メソッドが渡されたオブジェクトに対して何らかのアクションを実行すると想定しています。



operator()メソッドの動作は、T。パラメータの基本クラスに依存する必要があり、基本クラスでテンプレートを特殊化するだけでは機能しません。 そのため、2番目の引数をテンプレートに追加する必要があります。これは、特殊化のフラグとなり、もちろん、継承を自動的に確認する必要があります。



解決策があります。A。Alexandrescuの著書「Modern Design in C ++」のセクション「コンパイル段階での変換性と継承の認識」で説明されています。 アイデアは、異なるタイプのパラメーターを受け入れ、異なるタイプを返す関数オーバーロードを使用することです。 Alexandrescuはsizeofを使用して型を決定しました(私の手に渡ったエディションで)が、decltype演算子はC ++ 11標準に追加されました。 これにより、余分なコードを記述する必要がなくなります。



したがって、上記を念頭に置いてExecutorを書き直し、同時にoperator()メソッドの少なくともいくつかの実装を追加します。



 template<typename T, typename F> struct Executor { void operator()(const T&) { std::cout << " \n"; } }; template<typename T> struct Executor<T, Base1> { void operator()(const T&) { std::cout << "T   Base1\n"; } }; template<typename T> struct Executor<T, Base3> { void operator()(const T&) { std::cout << "T   Base3\n"; } };
      
      





クラスExecutorの特殊化が完了しました。継承の自動チェックを行うことは残っています。 これを行うには、オーバーロードされたセレクター関数を作成します。 呼び出されないので、気付く必要はありません。 decltype演算子による計算結果のタイプを取得する場合、計算自体は実行されません。



 void selector(...); Base1 selector(Base1*); Base3 selector(Base3*);
      
      





クラスへのポインターを使用してセレクター関数を呼び出すと、コンパイラーは最適なオプションを選択しようとします。 クラスがBase1またはBase3の継承者である場合、対応するメソッドが選択され、クラスが他の何かから継承される場合、可変数の引数を持つ関数が選択されます。



今、それを使用する方法について:



 void main() { Derived12 d12; Derived23 d23; double d; Executor<Derived12, decltype( selector( (Derived12*) 0 ) )>()( d12 ); Executor<Derived23, decltype( selector( (Derived23*) 0 ) )>()( d23 ); Executor<double, decltype( selector( (double*) 0 ) )>()( d ); }
      
      







次の行が画面に表示されます。

 Base1から継承されたT
 Base3から継承されたT
一般オプション




利便性と美しさのために、Executor :: operator()の呼び出しをテンプレート関数でラップできます。



 template<typename T> void execute(const T& v) { Executor<T, decltype( selector( (T*) 0 ) )>()( v ); } void main() { Derived12 d12; Derived23 d23; double d; execute( d12 ); execute( d23 ); execute( d ); }
      
      





悪くないように、それは判明しました。 さらに、Base2から継承する場合の動作をさらに特殊化します。 Executorクラスを特化する必要はありません。セレクター関数のオーバーロードを追加して、コンパイルを試みてください。 コンパイラーは、使用するセレクター関数のオプションを選択できないというエラーメッセージを表示します。 この状況を解決する方法は?



まず、Executorクラスの動作に影響を与える2つのクラスからクラスが同時に継承されたときに取得する動作を決定する必要があります。 いくつかのオプションを考えてみましょう。



1.クラスの1つがより優先され、2つ目のクラスは無視されます。

2.この状況では、特別な行動が必要です。

3.両方のクラスの処理を順番に呼び出す必要があります。



ポイント3はポイント2の特殊なケースであるため、考慮しません。



セレクター関数は、二重継承オプションを認識できる必要があります。 これを行うには、別の基本クラスへのポインターとなる2番目の引数を追加し、親Base1とBase2が存在する場合はBase1が優先され、Base2とBase3が存在する場合は特別な動作が必要であると仮定して問題を検討します。 この場合、セレクター関数とexecuteメソッドのオーバーロードは次のようになります。



 class Base23 {}; void selector(...); Base1 selector(Base1*, ...); Base1 selector(Base1*, Base2*); Base2 selector(Base2*, ...); Base23 selector(Base2*, Base3*); Base3 selector(Base3*, ...); template<typename T> void execute(const T& v) { Executor<T, decltype( selector( (T*) 0, (T*) 0 ) )>()( v ); }
      
      





Base23クラスは、テンプレートを特殊化するためにのみ使用されるため、実装は必要ありません。 Base23クラスの場合、実装は空の場合があります。実装がないと、セレクター関数のオーバーロードバージョンを定義するときにコンパイルエラーが発生します。 セレクター関数は2つのパラメーターを取り始めました。Base1、Base2、およびBase3からの同時継承がある場合、もう1つの引数を追加する必要があります。



基本クラスに応じてオブジェクトを処理する動作を特化する方法は、処理されるオプションの数が少ない場合に便利に使用されます。 たとえば、クラスがBase1、Base2、およびBase3を同時に継承する場合のみを考慮する必要があり、他のすべての場合の動作は同じになります。 項目3に関しては、複数の基本クラスが存在する場合、それぞれに対して順次処理を呼び出す必要がある場合、 タイプリストを使用する方が便利です。



何らかの理由でC ++ 11標準をサポートするコンパイラを使用できない場合、decltypeの代わりにsizeofを使用できます。 さらに、セレクター関数によって返される型のヘルパークラスを宣言する必要があります。 sizeof関数がこれらのクラスに対して異なる値を返すことが重要です。 この場合のテンプレートクラスのExecutorは、型ではなく整数値で特化する必要があります。 次のようになります。



 class IsUnknow { char c; } class IsBase1 { char c[2]; }; class IsBase23 { char c[3]; }; IsUnknow selector(...); IsBase1 selector(Base1*, ...); IsBase1 selector(Base1*, Base2*); IsBase23 selector(Base2*, Base3*); template<typename T> void execute(const T& v) { Executor<T, sizeof( selector( (T*) 0, (T*) 0 ) )>()( v ); } template<typename T, unsigned F> struct Executor { void operator(const T&); } template<typename T> struct Executor<T, sizeof(IsBase1) { void operator(const T&); } template<typename T> struct Executor<T, sizoef(IsBase23) { void operator(const T&); }
      
      







更新 :std :: enable_ifを使用して同様の動作を実装できますが、少し面倒ですが、条件はより明示的に設定されます。 ( Eivindlemeliskを追加してくれてありがとう

実装を表示...
 template<typename T> typename std::enable_if< !std::is_base_of<Base2, T>::value && !std::is_base_of<Base1, T>::value && !std::is_base_of<Base3, T>::value, void >::type execute(const T&) { cout << " \n"; } template<typename T> typename std::enable_if< std::is_base_of<Base1, T>::value && !std::is_base_of<Base2, T>::value, void >::type execute(const T&) { cout << "T   Base1\n"; } template<typename T> typename std::enable_if< std::is_base_of<Base1, T>::value && std::is_base_of<Base2, T>::value, void >::type execute(const T&) { cout << "T   Base1  Base2\n"; } template<typename T> typename std::enable_if< std::is_base_of<Base2, T>::value && !std::is_base_of<Base1, T>::value && !std::is_base_of<Base3, T>::value, void >::type execute(const T&) { cout << "T   Base2\n"; } template<typename T> typename std::enable_if< std::is_base_of<Base3, T>::value, void >::type execute(const T&) { cout << "T   Base3\n"; }
      
      









Update2 :セレクター関数への引数としてリンクを使用でき、Executor :: operator()の呼び出しがもう少しわかりやすくなります。
実装を表示...
 class Base23 {}; void selector(...); Base1 selector(const Base1&, ...); Base1 selector(const Base1&, const Base2&); Base2 selector(const Base2&, ...); Base23 selector(const Base2&, const Base3&); Base3 selector(const Base3&, ...); template<typename T> void execute(const T& v) { Executor<T, decltype( selector( v, v ) )>()( v ); }
      
      








All Articles