簡體   English   中英

如果我事先不知道所有的類,如何實現雙重調度?

[英]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*) 如果lhsA*則它將進行A::equalityCheck(A*) ,這將產生預期的(正確的)結果。 恭喜你!
  • 假設lhs是指向另一個也從Base派生的類X的指針。 在這種情況下,考慮到將XA進行比較, 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM