比较运算符重载

44

在这种情况下,最佳实践是什么:

bool Foo::operator==(const Foo& other) {
  return bar == other.bar;
}

// Implementation 1
bool Foo::operator!=(const Foo& other) {
  return bar != other.bar
}

// Implementation 2
bool Foo::operator!=(const Foo& other) {
  return !(*this == other);
}

对于像 >、<、<=、>= 这样的运算符,如果可能的话,我会选择实现方式 2。但是,对于 != 我认为实现方式 1 更好,因为不需要进行另一方法调用,这个想法正确吗?


我会以最自然的方式来表达它,以确保正确性。不论你选择哪种方式,编译器都很可能能够很好地编译它。 - Flexo
好的,谢谢。顺便提一下,我知道如果“==”过于复杂,那么实现方案2会更好,但那是另一种情况。 - blaze
你应该在每个函数中添加 const。此外,考虑使用自由函数而不是成员函数,因为前者在类型和转换方面对称,而后者则不是。如果你的类型可以从其他类型隐式转换,则这更加重要。 - David Rodríguez - dribeas
5个回答

37

第二种实现有一个显著的限制,即==始终是!=的布尔相反形式。这可能是您想要的,并且它使得您的代码更易于维护,因为您只需要更改一个实现来使两者保持同步。


8
地球上的每个 C++ 编译器都会将“!=”调用“==”进行内联处理。 - jthill
1
@jthill 小心:如果运算符尚未定义(例如源文件),它将无法工作。 - Tim
3
@Tim,如果你的编译器具有LTO功能,它就可以实现。 - zneak

18

在重载比较运算符时,您应该始终使用已有的内容。您只需要定义operator==operator<这两个就足够了,其他运算符可以依照这两个进行编写。这样做可以减少错误的发生,如果出现错误,只需在一个地方进行修正即可。

面向对象编程的主要特点之一是代码的可重用性。如果您已经编写好了代码,为什么还要再编写一遍呢? 坚持使用已有的内容,您只需要测试一个东西。

这有点像声明常量,然后在文件中的多个位置使用它。


1
实际上,您只需要使用 operator< 运算符即可。因为 x == y 在逻辑上等同于 !(x < y) && !(y < x)。 - mdenton8
@mdenton8,当然可以,但是如果您想要所有比较运算符,则定义operator<operator==是惯用方法。一些C++ STL算法和容器将使用operator==进行相等性比较,而有些则会按照您所描述的方式使用operator<。如果它们执行不同的操作,则在使用某些算法时可能会产生一些令人惊讶的结果。 - chris
没错,通常来说这样做更高效、更清晰。但出于你上面提到的原因,可维护性会降低。 - mdenton8
@mdenton8,嗯,我一直在思考为什么从头开始实现两者是惯用的做法,但我只能找到这个建议,而不是背后的原因。当然,你可以有一个定义等于性不同于等价性(使用operator<)的类,并且该类需要两者。然而,我通常只能想到一个一般情况下它们会分开的原因,那就是用operator<实现operator==将导致两个完整的比较而不是一个。我迫不及待地想要默认比较运算符,但它们很快就会变得非常有限。 - chris
19
实际上,你们全都错了。你们所做的一个大假设是“<”能够表达**线性排序**。但这可能并不是事实。例如,请考虑按家族血统排序的人们。这不是一个线性顺序,有些人是不能比较的,也就是对于我来说,X和我的兄弟Y既不满足X<Y也不满足X>Y,即它们都返回false。因此,通常情况下,X<Y **不等于** !(X>=Y)X==Y **不等于** !(X<Y) && !(Y<X) - freakish

5

实现方式2更好,因为它利用了已经定义的operator==运算符函数。此外,这些运算符函数应该是const的,因为它们不会修改对象。


我不能确定我的设置中到底是什么引起了这个问题(也许是 boost.optional),但我收到了一大堆错误信息,例如stl_algobase.h:808:22:error:no match for 'operator ==' 直到我将这些函数声明为“const”,才解决了这个问题。感谢您指出这一点。 - MatrixManAtYrService

3

以上都不是。

我希望我能找到那篇详细阐述这个问题的论文,但我想不起来它的名字了。

您的比较操作应该是外部的。您的接口应足以查找对象的状态,并且对象的状态应该决定比较。应该可以在类外部编写"equals",因此任何比较实际上都是可能的...您肯定会这么做。


2
这并不总是正确的。有些设计可能不希望授予对所有状态的访问权限。但是,您始终可以通过友元关系授予对自由函数的访问权限。 - David Rodríguez - dribeas

2

一般来说,实现2在很多方面都更好。首先,您不需要编写(几乎)重复的代码。如果您需要更改它(因为类已经增长或存在错误),则使用实现2再次只需更改1个地方。也就是说,实现2使您的代码更加一致和不容易出错。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接