当我不提前知道所有课程时,如何实施双重调度?

时间:2015-03-07 19:11:35

标签: c++ inheritance typeid double-dispatch extensible

我有一个基类(可能)有很多子类,我希望能够比较基类的任何两个对象是否相等。我试图在不调用亵渎性的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";
}

我理解写入的代码是失败的,因为equalityBounce()中的lhs是Base *,因此它甚至不知道带有A *的equalityCheck()的版本。但我不知道该怎么办。

3 个答案:

答案 0 :(得分:1)

如果你需要一个虚拟的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;
}

答案 1 :(得分:1)

为什么它不起作用

双重调度实现的问题在于您希望调用最具体的equalityCheck()

但是您的实现完全基于多态基类,并且equalityCheck(const A*)重载但不会覆盖 equalityCheck(const Base*)

Otherwhise说,在编译时编译器知道A::equalityBounce()可以调用equalityCheck(A*)(因为thisA*),但遗憾的是它调用了Base::equalityCheck()它没有A*参数的专用版本。

如何实施?

要使double dispatch生效,您需要在基类中具有double dispatched 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*),因为thisA*
  • 调用的Base *lhs对象将调用其equalityCheck(A*)。如果lhsA*,那么它将转到A::equalityCheck(A*),这将产生预期的(正确的)结果。 恭喜!
  • 假设lhs是指向另一个来自X的类Base的指针。在这种情况下,考虑到您将lhs->equalityCheck(A*)X::equalityCheck(A*)进行比较,X会调用A并返回正确的回复。

如何使其可扩展?双重调度图!

使用强类型语言进行双重调度的问题在于&#34;反弹&#34;对象需要知道如何与特定(事​​先已知)类进行比较。由于您的源对象和退回对象具有相同的多态基类型,因此基础需要知道所有涉及的类型。这种设计严重限制了可扩展性。

如果您希望能够在基类中预先知道任何派生类型,那么您必须通过动态类型(无论是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相同。如果我想允许用X复制A,我只需要定义一个比较函数:

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

答案 2 :(得分:0)

由于dynamic_cast导致Altough缓慢,我认为最舒适的解决方案是下一个:

#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.
}

通过这种方式,您只需要关心比较相同派生类型为“我”的对象。您不需要编写更多重载,因为BaseHelper会为您完成所有辅助工作。