应该在哪里!=运算符在类层次结构中定义?

时间:2015-07-07 09:31:43

标签: c++ operator-overloading comparison-operators

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

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

如您所见,operator!=在基类中定义。因为我非常懒,所以我不想在所有派生类中复制这么简单的代码。

Unfortunatley,使用此代码:

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返回false,因为它执行A::operator!=,然后调用A::operator==而不是B::operator==(即使运算符是虚拟的,因为派生类版本参数不同,它们没有在vtable中链接。)

那么对于类层次结构来说,最好的地址是什么?=运算符?

一种解决方案是在每个班级重复一次,B将有:

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

但是当你有很多课程时,这很痛苦......我有30岁......

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

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

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

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

2 个答案:

答案 0 :(得分:5)

您在B ...

中的功能
virtual bool operator==( const B& right ) const

... 覆盖A ...

中的功能
virtual bool operator==( const A& right ) const

...因为参数类型不同。 (只有协变返回类型才允许存在差异。)

如果您更正了这一点,那么您就可以选择如何将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将与不会引入其他数据成员的派生对象进行比较。

答案 1 :(得分:4)

这样的事情怎么样?

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,并从基类中定义的operator==operator!=调用该函数。

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

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