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

jpo*_*o38 7 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;
};
Run Code Online (Sandbox Code Playgroud)

如您所见,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!
Run Code Online (Sandbox Code Playgroud)

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

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

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

virtual bool operator!=( const B& right ) const
{
    return !( *this == right );
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

Ton*_*roy 6

你的功能B......

virtual bool operator==( const B& right ) const
Run Code Online (Sandbox Code Playgroud)

......不重写的功能A...

virtual bool operator==( const A& right ) const
Run Code Online (Sandbox Code Playgroud)

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

如果你纠正了这个,那么你就可以选择如何将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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,使用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);
}
Run Code Online (Sandbox Code Playgroud)

这具有与上述相同的问题B::operator==:例如,A将比较不等于未引入其他数据成员的派生对象.


San*_*ker 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;
};
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 这样你就得到了“B(42, 0) != A(42)”,但仍然是“A(42) == B(42, 0)”。这需要多次调度。 (2认同)