是==和!=相互依赖?

Bar*_*arc 292 c++ equality operator-overloading equality-operator

我学习C++操作符重载,我看到==!=仅仅是可定制的用户定义类型的一些特殊功能.但我担心的是,为什么需要两个单独的定义?我认为如果a == b是真的,则a != b自动为假,反之亦然,并且没有其他可能性,因为根据定义,a != b!(a == b).我无法想象任何情况都不是这样.但也许我的想象力是有限的,或者我对某些东西一无所知?

我知道我可以用另一个来定义一个,但这不是我要问的.我也没有询问按价值或身份比较对象之间的区别.或者两个对象是否可以同时相等且不相等(这绝对不是一个选项!这些东西是相互排斥的).我问的是这个:

是否有任何情况可以提出有关两个对象相等的问题是否有意义,但是询问它们相等是没有意义的?(无论是从用户的角度,还是从实施者的角度来看)

如果没有这种可能性,那么为什么地球上C++会将这两个运算符定义为两个不同的函数?

小智 272

希望语言自动重写a != b,!(a == b)因为a == b返回的内容不是a bool.你可以做到这一点有几个原因.

您可能有表达式构建器对象,其中a == b没有并且不打算执行任何比较,而只是构建一些表达式节点表示a == b.

您可能有懒惰的评估,其中a == b没有并且不打算直接执行任何比较,而是返回某种lazy<bool>可以bool在稍后时间隐式或显式转换为实际执行比较的类型.可能与表达式构建器对象结合使用,以便在评估之前完成表达式优化.

您可能有一些自定义optional<T>模板类,其中给定可选变量,t并且u您希望允许t == u,但让它返回optional<bool>.

可能还有更多我没想到的.即使在这些例子中,操作a == ba != b两者都有意义,但仍然a != b不是一样的!(a == b),所以需要单独的定义.

  • 表达式构建是一个很好的实际例子,当你想要它时,它不依赖于人为的场景. (71认同)
  • 所有这一切,和'南' - 请记住`NaN`s; (42认同)
  • "你可能有表达式构建器对象" - 然后运算符`!`也可以构建一些表达式节点,我们仍然可以用`!(a == b)`替换`a!= b`,就此而言去.同样适用于`lazy <bool> :: operator!`,它可以返回`lazy <bool>`.`optional <bool>`更有说服力,因为例如`boost :: optional`的逻辑真值取决于值是否存在,而不是值本身. (41认同)
  • @jsbueno:有人进一步指出,NaN在这方面并不特别. (9认同)
  • 另一个很好的例子是矢量逻辑运算.你宁愿一个人通过数据计算`!=`而不是两次通过计算`==`然后`!`.特别是在你不能依赖编译器融合循环的那一天.或者即使在今天,如果你没有说服编译器你的向量不重叠. (6认同)
  • @ ya23我希望我对Steve Jessop的回应更加清晰.在`lazy <T>`我记得,`a == b`会返回一个`lazy <bool>`.应用`!`将阻塞直到其值已知,然后否定该值."懒惰<T>"的其他方法也完全有效. (2认同)
  • @BarbaraKwarc在我的例子中,两个表达式的结果不一定相同.:)是的,我认为我同意让一个操作员工作而不是另一个操作员是没有意义的,或者至少如果有一个有意义的情况我无法想到它. (2认同)
  • 通常,您只需反转下一步操作的逻辑,例如将参数反转为混合指令.这将是一个有效的理由*不*提供一个'操作!=`.昂纳雾的VCL确实提供了一个,留给了程序员要记住,`!=`较慢,应尽量避免使用,但是这将是一个有效的设计选择这样做,和图书馆的强制用户写`!(a == b)`如果他们真的需要逆.或者仅在为支持AMD的XOP扩展的目标构建时提供`!=`.(最后一个不是*好*设计选择,但有效.) (2认同)

shr*_*ike 110

如果没有这种可能性,那么为什么地球上C++会将这两个运算符定义为两个不同的函数?

因为你可以重载它们,并且通过重载它们,你可以给它们与原始它们完全不同的含义.

例如,运算符<<,最初是按位左移运算符,现在通常作为插入运算符重载,如std::cout << something; 与原来完全不同的意思.

因此,如果您接受操作符的含义在重载时发生更改,那么就没有理由阻止用户向操作符赋予意义,==这不完全是操作符的否定!=,尽管这可能会令人困惑.

  • 这是唯一具有实际意义的答案. (18认同)
  • 对我来说,似乎你有倒退的因果关系.您可以单独重载它们,因为`==`和`!=`作为不同的运算符存在.另一方面,它们可能不存在作为不同的运算符,因为您可以单独重载它们,但是由于遗留和方便(代码简洁)的原因. (2认同)

Tre*_*key 60

但我担心的是,为什么需要两个单独的定义?

您不必同时定义它们.
如果它们是互斥的,那么您仍然可以通过仅定义==<旁边的std :: rel_ops来简明扼要

Fom cppreference:

#include <iostream>
#include <utility>

struct Foo {
    int n;
};

bool operator==(const Foo& lhs, const Foo& rhs)
{
    return lhs.n == rhs.n;
}

bool operator<(const Foo& lhs, const Foo& rhs)
{
    return lhs.n < rhs.n;
}

int main()
{
    Foo f1 = {1};
    Foo f2 = {2};
    using namespace std::rel_ops;

    //all work as you would expect
    std::cout << "not equal:     : " << (f1 != f2) << '\n';
    std::cout << "greater:       : " << (f1 > f2) << '\n';
    std::cout << "less equal:    : " << (f1 <= f2) << '\n';
    std::cout << "greater equal: : " << (f1 >= f2) << '\n';
}
Run Code Online (Sandbox Code Playgroud)

是否有任何情况可以提出有关两个对象相等的问题是否有意义,但是询问它们不相等是没有意义的?

我们经常将这些运算符与平等相关联
虽然这就是它们在基本类型上的行为方式,但没有义务这是它们在自定义数据类型上的行为.如果你不想,你甚至不需要返回布尔.

我看到人们以奇怪的方式超载运营商,却发现它对于他们的域特定应用是有意义的.即使界面看起来表明它们是互斥的,作者也可能想要添加特定的内部逻辑.

(无论是从用户的角度,还是从实施者的角度来看)

我知道你想要一个具体的例子,
所以这里有一个我认为实用的Catch测试框架:

template<typename RhsT>
ResultBuilder& operator == ( RhsT const& rhs ) {
    return captureExpression<Internal::IsEqualTo>( rhs );
}

template<typename RhsT>
ResultBuilder& operator != ( RhsT const& rhs ) {
    return captureExpression<Internal::IsNotEqualTo>( rhs );
}
Run Code Online (Sandbox Code Playgroud)

这些运算符正在做不同的事情,将一个方法定义为另一个方法(而不是另一个方法)是没有意义的.这样做的原因是框架可以打印出比较结果.为此,它需要捕获使用了重载运算符的上下文.

  • 哦,我怎么可能*不*了解`std :: rel_ops`?非常感谢您指出这一点. (14认同)
  • 来自cppreference(或其他任何地方)的近似逐字副本应清楚标记并正确归因.无论如何,`rel_ops`太可怕了. (5认同)
  • 您仍需要清楚地表明代码示例是来自cppreference的99%,而不是您自己的代码示例. (4认同)
  • Std :: relops似乎已经失宠了.查看boost操作以获得更具针对性的内容. (2认同)

Jan*_*der 42

有些情况下,一些非常完善的约定(a == b)(a != b)两个假不一定是对立的.特别是,在SQL中,与NULL的任何比较都会产生NULL,而不是true或false.

如果可能的话,创建新的示例可能不是一个好主意,因为它太不直观了,但是如果你想模拟一个现有的约定,那么选择让你的操作符为"正确"行为是很好的.上下文.

  • 在C++中实现类似SQL的null行为?Ewwww.但我认为这不是我认为应该用语言禁止的东西,不管它有多么令人反感. (4认同)
  • @Barmar:嗯不,那不是重点.OP已经知道这个事实,或者这个问题不存在.关键是要提供一个例子,它有意义1)实现`operator ==`或`operator!=`之一,而不是另一个,或者2)以不同的方式实现`operator!=`否定`operator ==`.并且为NULL值实现SQL逻辑不是这种情况. (2认同)
  • @dan1111 根据我使用 sql server 和 bigquery 的经验,`X == null` 和 `X != null` 肯定会评估为 `null`,而不是 `false`。我怎么知道,你可能会问?a) 这些值显示为 `null`,而不是 `false` b) `not (X == null)` 和 `not (X != null)` 不计算为 `true`,这是每个 sql 程序员的一课在某个时候学习......确实,我相信所有主要的 sql 实现都非常紧密地遵守(某些迭代)sql 标准。 (2认同)

Cen*_*ril 23

我只回答你问题的第二部分,即:

如果没有这种可能性,那么为什么地球上C++会将这两个运算符定义为两个不同的函数?

允许开发人员超载的一个原因是性能.您可以通过实现==和来实现优化!=.然后x != y可能比便宜!(x == y).有些编译器可能能够为您优化它,但也许不是,特别是如果您有涉及大量分支的复杂对象.

即使在Haskell中,开发者拿法律和数学概念非常重视,一个仍然允许超载既==/=,因为你可以在这里看到(http://hackage.haskell.org/package/base-4.9.0.0/docs/Prelude .html #v:-61--61-):

$ ghci
GHCi, version 7.10.2: http://www.haskell.org/ghc/  :? for help
?> :i Eq
class Eq a where
  (==) :: a -> a -> Bool
  (/=) :: a -> a -> Bool
        -- Defined in `GHC.Classes'
Run Code Online (Sandbox Code Playgroud)

这可能被认为是微优化,但在某些情况下可能是有保证的.

  • SSE(x86 SIMD)包装类就是一个很好的例子.有一个[`pcmpeqb`](http://www.felixcloutier.com/x86/PCMPEQB:PCMPEQW:PCMPEQD.html)指令,但是没有打包比较指令产生!=掩码.因此,如果你不能只是反转任何使用结果的逻辑,你必须使用另一条指令来反转它.(有趣的事实:AMD的XOP指令集确实对`neq`进行了打包比较.太糟糕了,英特尔没有采用/扩展XOP;在那个很快就要死的ISA扩展中有一些有用的指令.) (3认同)

Ben*_*ley 16

是否有任何情况可以提出有关两个对象相等的问题是否有意义,但是询问它们不相等是没有意义的?(无论是从用户的角度,还是从实施者的角度来看)

这是一个意见.也许它没有.但语言设计师并非无所不知,他们决定不限制那些可能会提出可能有意义的情况的人(至少对他们而言).


Nia*_*all 13

回应编辑;

也就是说,如果有些类型可能有操作员==而不是操作员!=,反之亦然,并且何时有意义.

一般的,不,这是没有意义的.平等和关系运营商通常都是成套的.如果有平等,那么不平等也是如此; 小于,则大于等与<=等类似的方法被施加到算术运算符,以及,它们也通常有自然的逻辑集.

这在std::rel_ops命名空间中得到证明.如果您实现了等于和小于运算符,那么使用该命名空间可以为您提供其他服务,根据您原来实现的运算符实现.

所有人都说,是否有条件或情况,一方不会立即指另一方,或无法以其他方式实施?是的,可能很少,但他们在那里; 再次,正如rel_ops它自己的名称空间所证明的那样.因此,允许它们独立实现允许您利用该语言以对代码的用户或客户端仍然自然且直观的方式获取您需要或需要的语义.

已经提到的懒惰评估就是一个很好的例子.另一个很好的例子是赋予它们语义,这些语义并不意味着平等或不平等.与此类似的示例是位移操作符<<>>用于流插入和提取.虽然在一般的圈子中可能不赞成,但在一些领域特定的领域,它可能是有意义的.


Tay*_*wee 12

如果==!=运算符实际上并不意味着相等,那么<<>>流运算符并不意味着位移.如果将符号视为某些其他概念,则它们不必相互排斥.

在平等方面,如果您的用例保证将对象视为不可比较,则可能有意义,因此每次比较都应返回false(如果您的运算符返回非bool,则返回非可比较的结果类型).我不能想到这是有必要的具体情况,但我可以看到它足够合理.


It'*_*ete 7

以强大的力量来负责任,或者至少是非常好的风格指南.

==并且!=可以超负荷做任何你想要的事情.这既是一种祝福,也是一种诅咒.有没有保证!=手段!(a==b).


Daf*_*Cao 6

enum BoolPlus {
    kFalse = 0,
    kTrue = 1,
    kFileNotFound = -1
}

BoolPlus operator==(File& other);
BoolPlus operator!=(File& other);
Run Code Online (Sandbox Code Playgroud)

我不能证明这个运算符重载是正确的,但在上面的例子中,不可能将其定义operator!=为"反向" operator==.

  • [你为什么要定义这样的枚举?](http://thedailywtf.com/articles/What_Is_Truth_0x3f_) (8认同)

小智 5

最后,您使用这些运算符检查的是表达式a == ba != b返回布尔值(truefalse).这些表达式在比较后返回一个布尔值,而不是互斥.