うまくいかない理由
二重ディスパッチの実装の問題は、最も具体的なものequalityCheck()
が呼び出されることを期待していることです。
しかし、実装は完全にポリモーフィックな基本クラスに基づいており、equalityCheck(const A*)
オーバーロードしますが、オーバーライドしません equalityCheck(const Base*)
!
別の言い方をすれば、コンパイル時にコンパイラーはA::equalityBounce()
が呼び出される可能性があることを認識していますがequalityCheck(A*)
(this
は であるためA*
)、残念ながらパラメーターBase::equalityCheck()
に特化したバージョンを持たない呼び出しを行いA*
ます。
実装方法は?
double ディスパッチが機能するには、基本クラスに double ディスパッチされた equalityCheck() の型固有の実装が必要です。
これが機能するには、ベースがその子孫を認識している必要があります。
struct A;
struct Base {
virtual bool operator==(const Base& rhs) const
{
return rhs.equalityBounce(this);
}
virtual bool equalityBounce(const Base* lhs) const = 0;
virtual bool equalityCheck(const Base* lhs) const = 0;
virtual bool equalityCheck(const A* lhs) const = 0;
};
struct A : public Base {
...
bool equalityBounce(const Base* lhs) const override{
return lhs->equalityCheck(this);
}
bool equalityCheck(const Base* rhs) const override {
return false;
}
bool equalityCheck(const A* rhs) const override{
return a == rhs->a;
}
};
override
関数が実際にベースの仮想関数をオーバーライドすることを確認するために を使用することに注意してください。
この実装では、次の理由で機能します。
A::equalityBounce()
電話しますBase::equalityCheck()
- この関数のオーバーロードされたすべてのバージョンの中
Base::equalityCheck(A*)
からthis
、A*
- 呼び出された
Base *lhs
オブジェクトはその を呼び出しますequalityCheck(A*)
。が である場合lhs
は、期待される (正しい) 結果を生成するために使用されますA*
。おめでとう !A::equalityCheck(A*)
- も から派生した
lhs
別のクラスへのポインタであるとします。この場合、を呼び出して、とを比較することを考慮して、正しい応答を返すこともできます。 X
Base
lhs->equalityCheck(A*)
X::equalityCheck(A*)
X
A
拡張可能にする方法は?ダブル発送マップ!
厳密に型指定された言語を使用した二重ディスパッチの問題は、「バウンスされた」オブジェクトが特定の (事前にわかっている) クラスと比較する方法を知る必要があることです。ソース オブジェクトとバウンスされたオブジェクトは同じポリモーフィック ベース タイプであるため、ベースは関連するすべてのタイプを認識する必要があります。この設計により、拡張性が大幅に制限されます。
基本クラスで事前に知らなくても派生型を追加できるようにする場合は、動的型 (dynamic_cast または typeid) を使用する必要があります。
ここで動的な拡張性を提案します。同じタイプの 2 つのオブジェクトを比較するためにシングル ディスパッチを使用し、それらの間で異なるタイプを比較するためにダブル ディスパッチ マップを使用します (何も宣言されていない場合、デフォルトで false を返します)。
struct Base {
typedef bool(*fcmp)(const Base*, const Base*); // comparison function
static unordered_map < type_index, unordered_map < type_index, fcmp>> tcmp; // double dispatch map
virtual bool operator==(const Base& rhs) const
{
if (typeid(*this) == typeid(rhs)) { // if same type,
return equalityStrict(&rhs); // use a signle dispatch
}
else { // else use dispatch map.
auto i = tcmp.find(typeid(*this));
if (i == tcmp.end() )
return false; // if nothing specific was foreseen...
else {
auto j = i->second.find(typeid(rhs));
return j == i->second.end() ? false : (j->second)(this, &rhs);
}
}
}
virtual bool equalityStrict(const Base* rhs) const = 0; // for comparing two objects of the same type
};
A クラスは次のように書き直されます。
struct A : public Base {
A(int eh) : a(eh) {}
int a;
bool equalityStrict(const Base* rhs) const override { // how to compare for the same type
return (a == dynamic_cast<const A*>(rhs)->a);
}
};
このコードを使用すると、任意のオブジェクトを同じタイプのオブジェクトと比較できます。拡張性を示すためにstruct X
、 と同じメンバーを持つを作成しましたA
。A を X と比較できるようにするには、比較関数を定義するだけです。
bool iseq_X_A(const Base*x, const Base*a) {
return (dynamic_cast<const X*>(x)->a == dynamic_cast<const A*>(a)->a);
} // not a member function, but a friend.
次に、動的二重ディスパッチを機能させるには、この関数を二重ディスパッチ マップに追加する必要があります。
Base::tcmp[typeid(X)][typeid(A)] = iseq_X_A;
その後、結果は簡単に確認できます。
Base *w = new A(1), *x = new A(2), *y = new X(2);
std::cout << (*w == *w) << "\n"; // true returned by A::equalityStrict
std::cout << (*w == *x) << "\n"; // false returned by A::equalityStrict
std::cout << (*y == *x) << "\n"; // true returned by isseq_X_A