5

C++ で、基本クラスへのポインターを持っているときに派生クラスの関数を呼び出すときに問題が発生しています。

編集:いくつかの回答はCRTPに私を紹介しました

しかし、私のポイントは、現在処理されているタイプを認識していないため、「Base *」ではなく「Base *」クラスへのポインターが必要であるということです (現在のインスタンスは、ある種のファクトリから作成されます)。

クラス:

class Base 
{
..
template<typename T>
func (T arg) { ... };
};

class Derived1 : public Base
{
...
template<typename T>
func (T arg) { ... };
};


class Derived1 : public Base
{
...
template<typename T>
func (T arg) { ... };
};

使用法:

int main()
{
   Base* BasePtr = new Derived1();

   // The expected function to be called is Derived1::func<int>()
   BasePtr->func<int>();

   return 0; // :)
}

言語が仮想テンプレート機能をサポートしていないため、func を仮想化できません。

クラスのみがテンプレート引数を持つ場合にのみ許可され、その中の関数にテンプレート引数がある場合は許可されません。

Boost.Serialization 内で解決された同様の問題を見たことがありますが、解決策を理解できませんでした。

ありがとう、

コビー・メイア

4

3 に答える 3

7

Curiously recurring template pattern(CTRP) を実装します。

図:

template<typename D>
class Base 
{
public:
    template<typename T>
    void func (T arg) 
    {
         static_cast<D*>(this)->func(arg);
    }
};

class Derived1 : public Base<Derived1>
{
public:
    template<typename T>
    void func (T arg) { /*...*/ }
};


Base<Derived1> *basePtr = new Base<Derived1>();
basePtr->func(100);
于 2011-05-17T05:57:08.547 に答える
6

2 つの既存のソリューションは、動的ポリモーフィズムを静的ポリモーフィズムと交換します。当面の問題の詳細がなければ、それが有効なアプローチであるかどうかを知ることはできません。これは基本的にポリモーフィック階層を壊すためです。CRTP では、単一の基本クラスではなく、それらのファミリーが存在します。Derived1とのオブジェクトは無関係であるため、同じコンテナー内に保持することはできませんDerived2... コードを共有することだけが必要な場合は優れたソリューションですが、動的なポリモーフィズムが必要な場合はそうではありません。同様の問題については、Visitor パターンと double-dispatch を参照してください。

動的ポリモーフィズムが必要な場合は、二重ディスパッチの実装を試みることができます (これは面倒ですが、階層が十分に小さければ実現可能です。基本的には、2 つの異なる階層を作成しますBase。 rooted atBaseには仮想メソッドapplyがあり、2 番目の階層には 1 番目の階層の型ごとに仮想関数があります。

class Base;
class Derived1;  // inherits from Base, implements Visitor
class Derived2;  // inherits from either Base or Derived2
struct Visitor {
   virtual void visit( Base& ) = 0;     // manually unrolled for all types
   virtual void visit( Derived1& ) = 0;
   virtual void visit( Derived2& ) = 0;
};
struct Base {
   virtual void apply( Visitor& v ) {   // manually replicate this in Derived1, 2
      v.visit( *this );
   }
   template <typename T> void foo(T);   // implement 
};

template <typename T>
struct FooCaller : Visitor {
    T& ref_value;
    FooCaller( T& v ) : ref_value(v) {}
    template <typename U> void call_foo( U& o ) {
       o.foo(ref_value);
    }
    virtual void visit( Base & b )      { call_foo(b); }
    virtual void visit( Derived1 & d1 ) { call_foo(d1); }
    virtual void visit( Derived2 & d2 ) { call_foo(d2); } 
};

私が使用した名前は Visitor パターンで一般的であり、このアプローチはそのパターンと非常によく似ています (あえて Visitor パターンとは呼びませんが、アプローチは似ているので、命名規則を借りただけです)。

ユーザーコードは次のようになります。

int main()                     // main returns int, not void!!!
{
   Base* BasePtr = new Derived1();
   int i = 5;
   FooCaller<int> c(i)
   BasePtr->apply(c);          // [1] magic happens here
}

事前に宣言する必要性はic(可能であれば) 関数への引数を参照から const 参照に変更することで解放できます。実際の魔法は、[1] では C++ の単一ディスパッチ メカニズムが への呼び出しをディスパッチするDerived1::applyことです。これは、 が指すオブジェクトの動的な型であるためBasePtrです。その時点でVisitor::visit( Derived1& )、それ自体を引数として呼び出します。これは、単一のディスパッチ メカニズムを介して に再びディスパッチされFooCaller<int>::visit( Derived1& )、その時点で、両方のオブジェクトが静的な型に解決されます。をFooCaller<int>::visit呼び出すとcall_foo、引数Uは であると推定されDerived1、呼び出すとDerived1::foo、引数は であると推定され、最終的にint呼び出すことになりDerived1::foo<int>ます...いくつかのループと間接的な方法があります...

特定のユース ケースによっては、これが複雑すぎる (CRTP のような静的ポリモーフィズムが機能する場合) か、保守が難しすぎる (階層が大きい場合: 階層内の新しい要素ごとに、階層内のすべてのBaseを更新する必要があります)。 ) したがって、この複雑さを回避できれば完璧です。ただし、場合によっては、これが必要になります。Visitor

また、これは最も複雑な完全に動的なソリューションであることに注意してください。実行時ポリモーフィズムである必要があるものに応じて、その間に他のオプションがあります...階層がショーツのビジターをモデル化している場合があります。内部でテンプレートにディスパッチされるさまざまな仮想関数を手動で展開するだけで済みます。その場合、上記の複雑さの半分は解消されます。

また、これはC ++では非常に珍しいことであり、実際の問題を説明すると、より簡単な解決策がある可能性があることに注意してください。あなたが述べたのは、元の問題に対する解決策の要件です。

于 2011-05-17T07:33:56.207 に答える
3

これを確認してください。CRTP の実装に役立ちます。

于 2011-05-17T05:59:55.380 に答える