[英]How can I implement double dispatch when I don't know all the classes in advance?
我有一個(可能)有很多子類的基類,並且我希望能夠比較基類的任何兩個對象是否相等。 我試圖做到這一點而不調用褻瀆性的typeid關鍵字。
#include <iostream>
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;
};
struct A : public Base {
A(int eh) : a(eh) {}
int a;
virtual bool equalityBounce(const Base* lhs) const{
return lhs->equalityCheck(this);
}
virtual bool equalityCheck(const Base* rhs) const {return false;}
virtual bool equalityCheck(const A* rhs) const {return a == rhs->a;}
};
int main() {
Base *w = new A(1), *x = new A(2);
std::cout << (*w == *w) << "\n";
std::cout << (*w == *x) << "\n";
}
我知道所寫的代碼失敗了,因為equityBounce()中的lhs是Base *,所以它甚至不知道帶有A *的equalCheck()的版本。 但是我不知道該怎么辦。
如果您需要一個虛擬的equals運算符,並且希望避免使用dynamic_cast,則可以在基類中添加一個指示類型的枚舉類型,並創建一個虛擬的getter,如果匹配則進行靜態強制轉換。
enum ClassType { AType, BType };
Class A
{
public:
virtual ClassType getType ();
virtual bool operator ==(const A& a ) const;
};
// in B implementation
virtual bool B::operator ==(const A& a)
{
if (!A::operator==(a)) return false;
if(a.getType () != ClassType::BType) return false;
const B& b = static_cast <const& B>(a);
// do B == checks
return true;
}
為什么它不起作用
雙調度實現的問題在於,您希望調用最特定的equalityCheck()
。
但是,您的實現完全基於多態基類,並且equalityCheck(const A*)
重載但不會覆蓋 equalityCheck(const Base*)
!
另一個說法是,在編譯時,編譯器知道A::equalityBounce()
可以調用equityCheck equalityCheck(A*)
(因為this
是一個A*
),但不幸的是,它調用的Base::equalityCheck()
沒有針對A*
專門版本。 A*
參數。
如何執行呢?
為了使雙調度正常工作,您需要在基類中具有雙調度的equityCheck()的特定於類型的實現。
為此,基地需要了解其后代:
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
是指向另一個也從Base
派生的類X
的指針。 在這種情況下,考慮到將X
與A
進行比較, lhs->equalityCheck(A*)
將調用X::equalityCheck(A*)
並返回正確的響應。 如何使其可擴展? 雙調度圖!
使用強類型語言進行雙重調度的問題在於,“退回”對象需要了解如何與特定(預先已知)的類進行比較。 由於您的源對象和反彈對象具有相同的多態基本類型,因此基本需要知道所有涉及的類型。 這種設計嚴重限制了可擴展性。
如果想在基類中不事先知道任何派生類型的情況下添加它,則必須遍歷動態類型(它是dynamic_cast或typeid):
我在這里為您提出動態可擴展性的建議。 它使用單分派比較兩個相同類型的對象,並使用雙分派映射比較它們之間的不同類型(如果未聲明,則默認返回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
由於dynamic_cast
,速度雖然很慢,但我認為最舒適的解決方案是下一個解決方案:
#include <iostream>
struct Base {
virtual bool operator==(const Base& rhs) const
{ return equalityBounce(&rhs); }
virtual bool equalityBounce(const Base* lhs) const = 0;
};
template<typename Derived>
struct BaseHelper : public Base
{
bool equalityBounce(const Base* rhs) const
{
const Derived* p_rhs = dynamic_cast<const Derived*>(rhs);
if (p_rhs == nullptr)
return false;
else
return p_rhs->equalityCheck
(reinterpeter_cast<const Derived*>(this));
}
virtual bool equalityCheck(const Derived*) const = 0;
};
struct A : public BaseHelper<A> {
A(int eh) : a(eh) {}
int a;
virtual bool equalityCheck(const A* rhs) const
{ return a == rhs->a; }
};
int main() {
Base *w = new A(1), *x = new A(2);
std::cout << (*w == *w) << "\n"; // Prints 1.
std::cout << (*w == *x) << "\n"; // Prints 0.
}
這樣,您只需要關心比較相同派生類型為“ me”的對象。 您不需要編寫更多的重載,因為BaseHelper可以為您完成所有輔助工作。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.