抽象型A
と2つの派生型A1
とがありA2
ます。
タイプAのパラメーターを受け取るクラスAにメソッドMを追加したいのですが、アドホック多相性が必要です。
確かに、私は3つの実装が必要です:A1::M(A1 a)
、、、。しかし、タイプAのポインターを使用してメソッドMを呼び出す抽象的な方法が必要です。A1::M(A2 a)
A2::(A1 a)
A2::M(A2 a)
私はすべての署名宣言をクラスA
に入れることができましたが、それはひどいです。
抽象型A
と2つの派生型A1
とがありA2
ます。
タイプAのパラメーターを受け取るクラスAにメソッドMを追加したいのですが、アドホック多相性が必要です。
確かに、私は3つの実装が必要です:A1::M(A1 a)
、、、。しかし、タイプAのポインターを使用してメソッドMを呼び出す抽象的な方法が必要です。A1::M(A2 a)
A2::(A1 a)
A2::M(A2 a)
私はすべての署名宣言をクラスA
に入れることができましたが、それはひどいです。
シミュレートされたダブルディスパッチを使用します。
class A {
public:
virtual void M(A &) = 0;
virtual void M(A1 &) = 0;
virtual void M(A2 &) = 0;
};
class A1 : public A {
public:
virtual void M(A &a) { a.M(*this); }
virtual void M(A1 &a) { std::cout << "A1 <- A1\n"; }
virtual void M(A2 &a) { std::cout << "A2 <- A1\n"; }
};
class A2 : public A {
public:
virtual void M(A &a) { a.M(*this); }
virtual void M(A1 &a) { std::cout << "A1 <- A2\n"; }
virtual void M(A2 &a) { std::cout << "A2 <- A2\n"; }
};
(たとえば、 http: //ideone.com/nyclsを参照してください。)
基本クラスの複数のオーバーロードを回避する方法はないと思います。
ポリモーフィックな動作が必要な場合、必要なのはBaseクラスAの1つのメソッドだけです。その後、そのメソッドをA1、A2に再実装できます。
その後、あなたは書くことができます:
A *a1 = new A1();
A *a2 = new A2();
a1->M(a2); //polymorphic behavior
あなたがこのようなものを作る場合:
struct A
{
virtual void M(A *a) {}
};
struct A1 : public A
{
virtual void M(A1 *a) {cout << "A1" << endl;}
virtual void M(A *a) {cout << "A" << endl;}
};
それで:
A1 * a1 = new A1();
a1->M(a1); //prints "A1"
A * a = a1;
a->M(a1); //prints "A"
私はこれがあなたが望む行動だとは思わない
そんなことしてみませんか?
void A1::M( A a )
{
if( dynamic_cast< A1* >( &a ) )
{
// do your A1::M1(A1 a) stuff
}
else
if( dynamic_cast< A2* >( &a ) )
{
// do your A1::M2(A2 a) stuff
}
else
{
throw std::logic_error( "Unsupported A type." );
}
}
そして、A2 :: Mについても同様に行いますか?
これはダブルディスパッチです。あなたが書くとき:
A* p1;
A* p2;
p1->M(*p2);
*p1
のタイプとのタイプの両方でディスパッチする必要があり*p2
ます。
始める前に、これはさまざまな派生型n^2
の関数を
意味することを理解する必要があります。n
そして、どこかで、誰かが派生型のすべてを知っている必要があります(未知の型のペアに対してある種の「デフォルト」の実装を定義できる場合を除く)。
これを実装する方法は2つあります。最も単純なのは、階層が閉じている場合(つまり、クライアントコードが新しい派生クラスを導入できない場合)、基本クラスの仮想関数のホストを使用します。これらは階層の外部で呼び出されるように設計されていないため、通常は保護されています。
// Forward references needed for all derived classes...
class A1;
class A2;
// ...
class A
{
protectd:
virtual void doM(A1* arg) = 0;
virtual void doM(A2* arg) = 0;
// ...
public:
virtual void M(A& arg) = 0;
};
派生クラスでは、の実装M
は常に同じです。
void A1::M(A& arg)
{
arg.doM( this );
}
これは単純で比較的効率的ですが、新しい派生クラスを追加するたびに、抽象ベースとすべての派生クラス(新しい仮想関数を実装する必要があります)を変更する必要があります。ただし、閉じた階層には便利です。さまざまな戦略がすべてソースファイルで定義され、クライアントに公開されていない、動作の一部に戦略パターンを使用するクラスで使用しました(戦略の抽象ベースは、ヘッダーで前方宣言されただけです)。 、したがって、戦略を追加した場合、ヘッダーの変更は必要ありませんでした)。
より一般的な解決策は、インデックスとしてstd::map
のペアを持つ、を
含みます。コピーできないため、直接typeid
使用することはできません。typeid
C++11はtype_index
それをラップするために提供します。古いコンパイラを使用している場合は、自分でコンパイラを実装するのはかなり簡単です。基本的な原則は、(おそらくA自体で)次のようなものです。
typedef std::pair<std::type_index, std::type_index> TypePairKey;
typedef void (*FuncPtr)( M* arg1, M* arg2 );
typedef std::unordered_map<TypePairKey, FuncPtr> DispatchMap;
static DispatchMap ourDispatchMap;
と:
void M( A& arg ) // NOT virtual !!!
{
DispatchMap::iterator entry
= ourDispatchMap.find(
DispatchMap::value_type( typeid( *this ), typeid( arg ) ) );
assert( entry != ourDispatchMap.end() );
// Or some default handling, maybe throw NotYetImplemented()
(*entry->second)( this, &arg );
}
本当の問題は、個々の関数のそれぞれを記述し、それらのアドレスをマップに挿入することです(最初に使用する前に)。もちろん、関数自体は、ここからのみ呼び出され、関係するクラスのフレンドになることができる場合は、を使用できますが、まだ
dynamic_cast
n2個あります。彼ら。(よくある解決策の1つは、それらをいずれかのクラスの静的メンバーにし、各派生クラスに、担当するすべての関数の登録を行う型の静的メンバーを定義させることです。)static_cast