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

标签 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() 版本。但我不知道该怎么办。

最佳答案

为什么不起作用

双重分派(dispatch)实现的问题是您希望调用最具体的 equalityCheck()

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

换句话说,编译器在编译时知道 A::equalityBounce() 可以调用 equalityCheck(A*)(因为 this是一个 A*),但不幸的是它调用了 Base::equalityCheck(),它没有针对 A* 参数的专门版本。

如何实现?

要使双重分派(dispatch)起作用,您需要在基类中具有双重分派(dispatch) equalityCheck() 的特定类型实现。

为此,Base 需要知道它的后代:

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 派生的。在这种情况下,考虑到您d 将 XA 进行比较。

如何让它可扩展?双分派(dispatch) map !

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

如果您希望能够在基类中事先不知道的情况下添加任何派生类型,那么您必须通过动态类型(无论是 dynamic_cast 还是 typeid):

我在这里向您提出动态可扩展性的建议。它使用单分派(dispatch)来比较两个相同类型的对象,并使用双分派(dispatch)映射来比较它们之间的不同类型(如果没有声明则默认返回 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.  

然后为了使动态双分派(dispatch)工作,我必须将这个函数添加到双分派(dispatch)映射中:

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

关于c++ - 如果事先不知道所有的类,如何实现双重调度?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28918873/

相关文章:

c++ - SHGetFolderPath' : identifier not found

c++ - 位字段结构分配意外行为

ruby - 如何在 Ruby 中调用特定祖先类的方法?

javascript - RequireJS 和类继承

c++ - 'typeid' 与 C++ 中的 'typeof'

c++ - 容器 STL 的 typeid

c++ try and catch 时的返回值

c++ - ATM 机编程挑战

C++ 错误 C2248 : cannot access private member declared in SUPER class

c++-cli - 为什么 'System::Guid' 类型的 switch 表达式是非法的?