!= 运算符应该在 class 层次结构中的什么地方定义?

Where should != operator be defined in a class hierarchy?

这是一个非常简单的 class 层次结构:

class A
{
public:
    A( int _a ) : a( _a ) {}

    virtual bool operator==( const A& right ) const
    {
        return a == right.a;
    }

    virtual bool operator!=( const A& right ) const
    {
        return !( *this == right );
    }

    int a;
};

class B : public A
{
public:
    B( int _a, int _b ) : A( _a ), b( _b ) {}

    virtual bool operator==( const B& right ) const
    {
        return A::operator==( right ) && b == right.b;
    }

    int b;
};

如您所见,运算符 != 定义在基 class 中。因为我很懒,不想在所有派生的classes.

中重复这么简单的代码

不幸的是,代码如下:

A a4(4), a5(5), a4bis(4);
assert( a4 == a4bis );
assert( a4 != a5 );

B b1(4,5), b2(4,6);
assert( !(b1 == b2) );
assert( b1 != b2 ); // fails because B::operator== is not called!

b1 != b2 returns false,因为它执行 A::operator!= 然后调用 A::operator== 而不是 B::operator== (即使运算符是虚拟的,因为派生class 版本参数不同,它们在 vtable 中没有链接)。

那么以通用方式为 class 层次结构寻址 != 运算符的最佳方式是什么?

一个解决方案是在每个 class 中重复它,然后 B 将有:

virtual bool operator!=( const B& right ) const
{
    return !( *this == right );
}

但是当你有很多 classes 时,这很痛苦....我有 30....

另一种解决方案是采用通用模板方法:

template <class T>
bool operator!=( const T& left, const T& right )
{
    return !( left == right );
}

但这会绕过任何 class 定义的任何 != 运算符...因此,如果有人以不同的方式声明它(或者如果有人声明 == 本身调用,则可能会有风险!=,它会以无限循环结束......)。所以我觉得这个解决方案非常不安全....除非我们可以限制模板用于从我们的层次结构的顶层 class 派生的所有 classes (A在我的示例中)...但我认为这根本不可行。

注意:我还没有使用 C++11...对此感到抱歉。

你的函数在B...

virtual bool operator==( const B& right ) const

...不会覆盖A中的函数...

virtual bool operator==( const A& right ) const

...因为参数类型不同。 (差异仅允许用于协变 return 类型。)

如果您更正此问题,您将能够选择如何将 B 对象与其他 AA 派生的对象进行比较,例如也许:

bool operator==( const A& right ) const override
{
    if (A::operator==( right ))
        if (typeid(*this) == typeid(right))
            return b == static_cast<const B&>(right).b;
    return false;
}

请注意,使用上面的 typeid 比较意味着只有 B 对象将比较相等:任何 B 将与任何 B 派生对象比较不相等。这可能是也可能不是您想要的。

有了 B::operator== 的实现,现有的 != 实现将正确包装 operator==。正如 Jarod42 所观察到的,您的 A::operator== 并不健壮,因为当左侧值是 A 时,只会比较右侧对象的 A 切片...您可能更喜欢:

virtual bool operator==(const A& right) const
{
    return a == right.a && typeid(*this) == typeid(right);
}

这与上面的 B::operator== 存在相同的问题:例如A 将不等于未引入更多数据成员的派生对象。

这样的事情怎么样?

class A {
  protected :
    virtual bool equals(const A& right) const {
      return (a == right.a);
    }

  public :
    A(int _a) : a(_a) { }

    bool operator==(const A& right) const {
      return this->equals(right);
    }
    bool operator!=(const A& right) const {
      return !(this->equals(right));
    }

    int a;
};

class B : public A {
  protected :
    virtual bool equals(const A& right) const {
      if (const B* bp = dynamic_cast<const B*>(&right)) {
        return A::equals(right) && (b == bp->b);
      }
      return false;
    }

  public :
    B(int _a, int _b) : A(_a), b(_b) { }

    int b;
};

将比较逻辑移动到单独的(虚拟)函数 equals,并从基 class 中定义的 operator==operator!= 调用该函数。

不需要在派生的 classes 中重新定义运算符。要更改派生 class 中的比较,只需覆盖 equals.

请注意,上面代码中的dynamic_cast用于确保运行时类型是执行比较的有效类型。 IE。对于 B::equals,它用于确保 rightB - 这是必要的,否则 right 将不会有 b 成员。