简体   繁体   English

如果我事先不知道所有的类,如何实现双重调度?

[英]How can I implement double dispatch when I don't know all the classes in advance?

I've got a base class with (potentially) a lot of subclasses, and I would like to be able to compare any two objects of the base class for equality. 我有一个(可能)有很多子类的基类,并且我希望能够比较基类的任何两个对象是否相等。 I am trying to do this without invoking the blasphemous typeid keyword. 我试图做到这一点而不调用亵渎性的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";
}

I understand that the code as written is failing because lhs in equalityBounce() is a Base*, so it doesn't even know about the version of equalityCheck() that takes an A*. 我知道所写的代码失败了,因为equityBounce()中的lhs是Base *,所以它甚至不知道带有A *的equalCheck()的版本。 But I don't know what to do about it. 但是我不知道该怎么办。

If you need a virtual equals operator and you want to avoid a dynamic_cast you could add an enum type to your base class that indicates type and make a virtual getter and then do static cast if it matches. 如果您需要一个虚拟的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;
}

Why it doesn't work 为什么它不起作用

The problem with your double dispatch implementation is that you expect that the most specific equalityCheck() is called. 双调度实现的问题在于,您希望调用最特定的equalityCheck()

But your implementation is totaly based on a polymorphic base class, and equalityCheck(const A*) overloads but does not override equalityCheck(const Base*) ! 但是,您的实现完全基于多态基类,并且equalityCheck(const A*)重载但不会覆盖 equalityCheck(const Base*)

Otherwhise said, at compile time the compiler knows that A::equalityBounce() could call equalityCheck(A*) (because this is an A* ), but unfortunately it calls Base::equalityCheck() which has no specialised version for an A* parameter. 另一个说法是,在编译时,编译器知道A::equalityBounce()可以调用equityCheck equalityCheck(A*) (因为this是一个A* ),但不幸的是,它调用的Base::equalityCheck()没有针对A*专门版本。 A*参数。

How to implement it ? 如何执行呢?

For the double dispatch to work, you need to have type specific implementations of the double dispatched equalityCheck() in your base class. 为了使双调度正常工作,您需要在基类中具有双调度的equityCheck()的特定于类型的实现。

For this to work, the Base needs to be aware of its descendents: 为此,基地需要了解其后代:

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;
    }
};

Note the use of override to make sure that the function really overrides a virtual function of the base. 请注意,使用override来确保该函数确实重写了基类的虚函数。

With this implementation it will work, because: 通过此实现,它将起作用,因为:

  • A::equalityBounce() will call Base::equalityCheck() A::equalityBounce()将调用Base::equalityCheck()
  • among all the overloaded versions of this function, it will choose the Base::equalityCheck(A*) because this is an A* 在此函数的所有重载版本中,它将选择Base::equalityCheck(A*)因为this是一个A*
  • the invoked Base *lhs object will call its equalityCheck(A*) . 被调用的Base *lhs对象将调用其equalityCheck(A*) If lhs is an A* it will hence go for A::equalityCheck(A*) which will produce the expected (correct) result. 如果lhsA*则它将进行A::equalityCheck(A*) ,这将产生预期的(正确的)结果。 Congratulations ! 恭喜你!
  • suppose lhs would be a pointer to another class X also derived from Base . 假设lhs是指向另一个也从Base派生的类X的指针。 In this case, lhs->equalityCheck(A*) would call X::equalityCheck(A*) and could also return the correct response, taking into consideration that you'd compare an X with an A . 在这种情况下,考虑到将XA进行比较, lhs->equalityCheck(A*)将调用X::equalityCheck(A*)并返回正确的响应。

How to make it extensible ? 如何使其可扩展? The double-dispatch map ! 双调度图!

The problem with the double dispatch using a strongly typed language, is that the "bounced" object needs to know about how to compare to specific (known in advance) classes. 使用强类型语言进行双重调度的问题在于,“退回”对象需要了解如何与特定(预先已知)的类进行比较。 As your source object and your bounced object are of the same polymorphic base type, the base needs hence to know all involved types. 由于您的源对象和反弹对象具有相同的多态基本类型,因此基本需要知道所有涉及的类型。 This design limits seriously the extensivbility. 这种设计严重限制了可扩展性。

If you want to be able to add any derived type without knowing it in advance in the base class, then you have to go through dynamic types (be it dynamic_cast or typeid): 如果想在基类中不事先知道任何派生类型的情况下添加它,则必须遍历动态类型(它是dynamic_cast或typeid):

I propose you here a proposal for dynamic EXTENSIBILITY. 我在这里为您提出动态可扩展性的建议。 It uses single dispatch for comparing two objects of the same type, and a double-dispatch map to compare different types between them (returning by default false if nothing was declared): 它使用单分派比较两个相同类型的对象,并使用双分派映射比较它们之间的不同类型(如果未声明,则默认返回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
};  

The A class would then be rewrtitten as: 然后将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); 
        }
};

With this code, you can compare any objects with an object of the same type. 使用此代码,您可以将任何对象与相同类型的对象进行比较。 Now to show extensibility, I've created a struct X , with the same members than A . 现在为了显示可扩展性,我创建了一个struct X ,其成员与A相同。 If I want to allow to copare A with X, I just have to define a comparison function: 如果我想让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.  

Then to make dynamic double dipatch work, I have to add this function to the double-dispatch map: 然后,要使动态双倍增补齐工作,我必须将此函数添加到双倍增补图中:

Base::tcmp[typeid(X)][typeid(A)] = iseq_X_A;

Then the resutls are easy to verify: 然后,结果很容易验证:

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

Altough slow due to the dynamic_cast , I think the most comfortable solution is the next one: 由于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.
}

In this way, you have to only to be care about comparing objects of same derived typed as "me". 这样,您只需要关心比较相同派生类型为“ me”的对象。 You don't need to write more overloads because BaseHelper makes all the auxiliary work for you. 您不需要编写更多的重载,因为BaseHelper可以为您完成所有辅助工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM