【问题标题】:Where should != operator be defined in a class hierarchy?!= 运算符应该在类层次结构中定义在哪里?
【发布时间】:2015-09-24 17:18:58
【问题描述】:

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

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

如您所见,运算符 != 是在基类中定义的。因为我很懒,不想在所有的派生类里都复制这么简单的代码。

不幸的是,使用此代码:

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……很抱歉。

【问题讨论】:

  • 此外,目前,A(42) == B(42, 0) 仅比较 A 部分...
  • 为了清楚起见并确保 A 继续独立工作(如果您不想要它,则不需要从中派生 B),实现 != for A, B, C , D 以及你在你的层次结构中拥有的任何东西。同样,如果您不需要它们,为什么还需要派生?

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


【解决方案1】:

你在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 将与未引入更多数据成员的派生对象进行比较。

【讨论】:

  • 那些 dynamic_cast 和 typeid 真的安全吗(即使使用模板类,因为我的类hirerachy 有模板......)?
  • @jpo38:它们很安全,是的...将为每个模板实例化创建不同的 RTTI/typeinfo 对象。
  • 谢谢。您的代码运行良好,但我更喜欢 Sander De Dycker 解决方案,因为我更喜欢派生类 (B) 将 A 对象强制转换为 B,而不是让父类 (A) 强制转换以按照您的建议进行强制转换(这可能会导致如果类层次结构很大,则代码难以阅读)。
  • @jpo38:我没有查看他的代码进行比较,但根据您提到的具体标准 - “而不是让父类 (A) 进行转换来进行转换你提议” - 我根本没有提议......上面的 only 演员表在 B::operator== 内,它将其他 B 对象转换为 B&amp; 以访问他们的b 会员。也许您误认为该代码是 A::operator== - override 表明它是 B
  • 好的,你说得对,对不起。无论如何,我投票给你们两个;-)。谢谢!
【解决方案2】:

这样的事情怎么样?

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 成员。

【讨论】:

  • 有了B(42, 0) != A(42),但仍然是A(42) == B(42, 0)。这将需要多次调度。
  • 你能多谈谈你的方法吗?谢谢:-)
  • @Jarod42 :这是您做出的选择(我从 OP 复制了该行为)。如果您希望相等运算符是可交换的,那么双重调度(如您所建议的)确实可以提供帮助。
  • 谢谢!每个类都可以覆盖第三个equals 函数是解决我的问题的好方法。
  • ...根据这个 double dispatch 事情:我对没有任何警告的 answer 有一种不好的感觉 - question 是关于在一个层次结构与众不同 (!=)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-09-14
  • 1970-01-01
  • 1970-01-01
  • 2021-03-28
  • 1970-01-01
  • 2021-12-30
  • 2017-11-30
相关资源
最近更新 更多