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

我知道所写的代码失败了,因为equalsBounce()中的lhs是Base *,因此它甚至不知道带有A *的equalCheck()的版本。但是我不知道该怎么办。

最佳答案

为什么不起作用

双调度实现的问题在于,您希望调用最特定的equalityCheck()

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

另一个说法是,在编译时,编译器知道A::equalityBounce()可以调用equalityCheck(A*)(因为thisA*),但是不幸的是,它调用的Base::equalityCheck()没有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*),因为thisA*
  • 被调用的Base *lhs对象将调用其equalityCheck(A*)。如果lhsA*,则它将使用A::equalityCheck(A*),它将产生预期的(正确)结果。 恭喜!
  • 假设lhs将是另一个从X派生的类Base的指针。在这种情况下,考虑到您将lhs->equalityCheck(A*)X::equalityCheck(A*)进行比较,X将调用A并返回正确的响应。

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

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

    如果您想添加任何派生类型而不在基类中预先知道它,那么您就必须遍历动态类型(它是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.
    

    然后,要使动态双倍增补齐工作,我必须将此函数添加到双倍增补图中:
    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/

    10-09 06:11