C++ 为类层次结构重载 operator== 的正确方法是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1691007/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 20:54:36  来源:igfitidea点击:

What's the right way to overload operator== for a class hierarchy?

c++operator-overloading

提问by Michael Kristofik

Suppose I have the following class hierarchy:

假设我有以下类层次结构:

class A
{
    int foo;
    virtual ~A() = 0;
};

A::~A() {}

class B : public A
{
    int bar;
};

class C : public A
{
    int baz;
};

What's the right way to overload operator==for these classes? If I make them all free functions, then B and C can't leverage A's version without casting. It would also prevent someone from doing a deep comparison having only references to A. If I make them virtual member functions, then a derived version might look like this:

重载operator==这些类的正确方法是什么?如果我将它们全部设为免费功能,那么 B 和 C 无法在不进行转换的情况下利用 A 的版本。它还可以防止有人在只引用 A 的情况下进行深度比较。 如果我将它们设为虚拟成员函数,那么派生版本可能如下所示:

bool B::operator==(const A& rhs) const
{
    const B* ptr = dynamic_cast<const B*>(&rhs);        
    if (ptr != 0) {
        return (bar == ptr->bar) && (A::operator==(*this, rhs));
    }
    else {
        return false;
    }
}

Again, I still have to cast (and it feels wrong). Is there a preferred way to do this?

再说一次,我还是要投(而且感觉不对)。有没有首选的方法来做到这一点?

Update:

更新:

There are only two answers so far, but it looks like the right way is analogous to the assignment operator:

到目前为止只有两个答案,但看起来正确的方法类似于赋值运算符:

  • Make non-leaf classes abstract
  • Protected non-virtual in the non-leaf classes
  • Public non-virtual in the leaf classes
  • 使非叶类抽象
  • 非叶类中的受保护非虚拟
  • 叶类中的公共非虚拟

Any user attempt to compare two objects of different types will not compile because the base function is protected, and the leaf classes can leverage the parent's version to compare that part of the data.

任何用户尝试比较不同类型的两个对象都不会编译,因为基函数是受保护的,并且叶类可以利用父类的版本来比较该部分数据。

采纳答案by CB Bailey

For this sort of hierarchy I would definitely follow the Scott Meyer's Effective C++ advice and avoid having any concrete base classes. You appear to be doing this in any case.

对于这种层次结构,我肯定会遵循 Scott Meyer 的 Effective C++ 建议并避免使用任何具体的基类。无论如何,您似乎都在这样做。

I would implement operator==as a free functions, probably friends, only for the concrete leaf-node class types.

我会operator==作为一个免费的函数来实现,可能是朋友,只针对具体的叶节点类类型。

If the base class has to have data members, then I would provide a (probably protected) non-virtual helper function in the base class (isEqual, say) which the derived classes' operator==could use.

如果基类必须具有数据成员,那么我将isEqual在派生类operator==可以使用的基类(比如说)中提供一个(可能受保护的)非虚拟辅助函数。

E.g.

例如

bool operator==(const B& lhs, const B& rhs)
{
    lhs.isEqual( rhs ) && lhs.bar == rhs.bar;
}

By avoiding having an operator==that works on abstract base classes and keeping compare functions protected, you don't ever get accidentally fallbacks in client code where only the base part of two differently typed objects are compared.

通过避免operator==在抽象基类上工作并保护比较函数,您永远不会在客户端代码中意外回退,其中只比较两个不同类型对象的基本部分。

I'm not sure whether I'd implement a virtual compare function with a dynamic_cast, I would be reluctant to do this but if there was a proven need for it I would probably go with a pure virtual function in the base class (notoperator==) which was then overriden in the concrete derived classes as something like this, using the operator==for the derived class.

我不确定我是否会用 a 实现一个虚比较函数dynamic_cast,我不愿意这样做,但如果确实需要它,我可能会在基类中使用纯虚函数(不是operator==)然后在具体派生类中像这样覆盖,使用operator==派生类。

bool B::pubIsEqual( const A& rhs ) const
{
    const B* b = dynamic_cast< const B* >( &rhs );
    return b != NULL && *this == *b;
}

回答by Job

I was having the same problem the other day and I came up with the following solution:

前几天我遇到了同样的问题,我想出了以下解决方案:

struct A
{
    int foo;
    A(int prop) : foo(prop) {}
    virtual ~A() {}
    virtual bool operator==(const A& other) const
    {
        if (typeid(*this) != typeid(other))
            return false;

        return foo == other.foo;
    }
};

struct B : A
{
    int bar;
    B(int prop) : A(1), bar(prop) {}
    bool operator==(const A& other) const
    {
        if (!A::operator==(other))
            return false;

        return bar == static_cast<const B&>(other).bar;
    }
};

struct C : A
{
    int baz;
    C(int prop) : A(1), baz(prop) {}
    bool operator==(const A& other) const
    {
        if (!A::operator==(other))
            return false;

        return baz == static_cast<const C&>(other).baz;
    }
};

The thing I don't like about this is the typeid check. What do you think about it?

我不喜欢这个的事情是 typeid 检查。你怎么看待这件事?

回答by marcinj

If you dont want to use casting and also make sure you will not by accident compare instance of B with instance of C then you need to restructure your class hierarchy in a way as Scott Meyers suggests in item 33 of More Effective C++. Actually this item deals with assignment operator, which really makes no sense if used for non related types. In case of compare operation it kind of makes sense to return false when comparing instance of B with C.

如果您不想使用强制转换,并确保不会意外地将 B 的实例与 C 的实例进行比较,那么您需要按照 Scott Meyers 在 More Effective C++ 的第 33 项中建议的方式重构您的类层次结构。实际上这一项涉及赋值运算符,如果用于非相关类型,这真的没有意义。在比较操作的情况下,在比较 B 和 C 的实例时返回 false 是有意义的。

Below is sample code which uses RTTI, and does not divide class hierarchy into concreate leafs and abstract base.

下面是使用 RTTI 的示例代码,并且没有将类层次结构划分为 concreate 叶和抽象基。

The good thing about this sample code is that you will not get std::bad_cast when comparing non related instances (like B with C). Still, the compiler will allow you to do it which might be desired, you could implement in the same manner operator< and use it for sorting a vector of various A, B and C instances.

这个示例代码的好处是,在比较不相关的实例(如 B 和 C)时,您不会得到 std::bad_cast。尽管如此,编译器将允许您执行可能需要的操作,您可以以相同的方式实现 operator< 并将其用于对各种 A、B 和 C 实例的向量进行排序。

live

居住

#include <iostream>
#include <string>
#include <typeinfo>
#include <vector>
#include <cassert>

class A {
    int val1;
public:
    A(int v) : val1(v) {}
protected:
    friend bool operator==(const A&, const A&);
    virtual bool isEqual(const A& obj) const { return obj.val1 == val1; }
};

bool operator==(const A& lhs, const A& rhs) {
    return typeid(lhs) == typeid(rhs) // Allow compare only instances of the same dynamic type
           && lhs.isEqual(rhs);       // If types are the same then do the comparision.
}

class B : public A {
    int val2;
public:
    B(int v) : A(v), val2(v) {}
    B(int v, int v2) : A(v2), val2(v) {}
protected:
    virtual bool isEqual(const A& obj) const override {
        auto v = dynamic_cast<const B&>(obj); // will never throw as isEqual is called only when
                                              // (typeid(lhs) == typeid(rhs)) is true.
        return A::isEqual(v) && v.val2 == val2;
    }
};

class C : public A {
    int val3;
public:
    C(int v) : A(v), val3(v) {}
protected:
    virtual bool isEqual(const A& obj) const override {
        auto v = dynamic_cast<const C&>(obj);
        return A::isEqual(v) && v.val3 == val3;
    }
};

int main()
{
    // Some examples for equality testing
    A* p1 = new B(10);
    A* p2 = new B(10);
    assert(*p1 == *p2);

    A* p3 = new B(10, 11);
    assert(!(*p1 == *p3));

    A* p4 = new B(11);
    assert(!(*p1 == *p4));

    A* p5 = new C(11);
    assert(!(*p4 == *p5));
}

回答by Mark Ransom

If you make the reasonable assumption that the types of both objects must be identical for them to be equal, there's a way to reduce the amount of boiler-plate required in each derived class. This follows Herb Sutter's recommendationto keep virtual methods protected and hidden behind a public interface. The curiously recurring template pattern (CRTP)is used to implement the boilerplate code in the equalsmethod so the derived classes don't need to.

如果您做出合理的假设,即两个对象的类型必须相同才能使它们相等,则有一种方法可以减少每个派生类中所需的样板数量。这遵循Herb Sutter 的建议,即保护虚拟方法并将其隐藏在公共接口后面。该奇异递归模板模式(CRTP)来实现的样板代码的equals方法,从而派生类不需要。

class A
{
public:
    bool operator==(const A& a) const
    {
        return equals(a);
    }
protected:
    virtual bool equals(const A& a) const = 0;
};

template<class T>
class A_ : public A
{
protected:
    virtual bool equals(const A& a) const
    {
        const T* other = dynamic_cast<const T*>(&a);
        return other != nullptr && static_cast<const T&>(*this) == *other;
    }
private:
    bool operator==(const A_& a) const  // force derived classes to implement their own operator==
    {
        return false;
    }
};

class B : public A_<B>
{
public:
    B(int i) : id(i) {}
    bool operator==(const B& other) const
    {
        return id == other.id;
    }
private:
    int id;
};

class C : public A_<C>
{
public:
    C(int i) : identity(i) {}
    bool operator==(const C& other) const
    {
        return identity == other.identity;
    }
private:
    int identity;
};

See a demo at http://ideone.com/SymduV

http://ideone.com/SymduV查看演示

回答by Denis Yaroshevskiy

  1. I think this looks weird:

    void foo(const MyClass& lhs, const MyClass& rhs) {
      if (lhs == rhs) {
        MyClass tmp = rhs;
        // is tmp == rhs true?
      }
    }
    
  2. If implementing operator== seems like a legit question, consider type erasure (consider type erasure anyways, it's a lovely technique). Here is Sean Parent describing it.Then you still have to do some multiple-dispatching. It's an unpleasant problem. Here is a talk about it.

  3. Consider using variants instead of hierarchy. They can do this type of things easyly.

  1. 我觉得这看起来很奇怪:

    void foo(const MyClass& lhs, const MyClass& rhs) {
      if (lhs == rhs) {
        MyClass tmp = rhs;
        // is tmp == rhs true?
      }
    }
    
  2. 如果实现 operator== 似乎是一个合法的问题,请考虑类型擦除(无论如何都要考虑类型擦除,这是一种可爱的技术)。这是 Sean Parent 的描述。那你还是要做一些多分派。这是一个令人不快的问题。这是一个关于它的讨论。

  3. 考虑使用变体而不是层次结构。他们可以轻松完成此类事情。