== ==和`!=`是否相互依赖?

我正在学习C ++中的运算符重载,我发现==!=只是一些特殊的函数,可以为用户定义的types定制。 但是,我担心为什么需要两个单独的定义? 我认为如果a == b是真的,那么a != b自动为false,反之亦然,并且没有其他可能性,因为根据定义, a != b!(a == b) 。 而且我无法想象任何情况都不是这样的。 但也许我的想象力是有限的,或者我对某些事情一无所知?

我知道我可以用另一个来定义一个,但这不是我所问的。 我也没有问及通过价值或身份来比较对象的区别。 或者两个对象是否可以同时相等和不相等(这绝对不是一种select!这些东西是相互排斥的)。 我问的是这样的:

有没有什么情况可以提出关于两个对象相同的问题是有道理的,但是询问他们是不是平等的没有意义? (无论是从用户的angular度,还是从实施者的angular度来看)

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

a == b返回除bool以外的内容时,您希望语言自动将a != b重写为!(a == b) 。 有几个原因可以让你做到这一点。

您可能有expression式构build器对象,其中a == b不是,并且不打算执行任何比较,而只是构build表示节点a == bexpression式节点。

你可能有一个懒惰的评估,其中a == b不是直接执行任何比较,而是返回某种lazy<bool> ,可以在稍后时间隐式或显式地转换为bool进行比较。 可能与expression式构build器对象结合,以便在评估之前进行完整的expression式优化。

你可能有一些自定义的optional<T>模板类,其中给定的可选variablestu ,你想允许t == u ,但是使它返回optional<bool>

可能还有更多的我没有想到。 即使在这些例子中, a == ba != b这两个操作都是有意义的,但是a != b!(a == b)不是一回事,因此需要单独的定义。

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

因为你可以超载他们,超载他们可以给他们一个完全不同的含义,从他们原来的。

以运算符<<为例,原本是按位左移运算符,现在通常作为插入运算符重载,就像在std::cout << something ; 完全不同于原来的意思。

所以,如果你接受一个运算符的含义在你超载的时候改变了,那么没有任何理由阻止用户赋予运算符==的含义,这不完全是运算符!=否定 ,尽pipe这可能是令人困惑的。

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

你不必定义两者。
如果它们是互斥的,只要定义==<和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'; } 

有没有什么情况可以提出关于两个对象相同的问题是有道理的,但是询问他们是不是平等的没有意义?

我们经常把这些运营商联系起来。
虽然这是他们在基本types上的行为,但没有义务这是他们在自定义数据types上的行为。 如果你不想要,你甚至不需要返回一个布尔值。

我见过人们以奇怪的方式重载操作符,只是发现它们对于特定于领域的应用程序是有意义的。 即使界面似乎表明它们是相互排斥的,作者也许想要添加特定的内部逻辑。

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

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

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

这些操作员正在做不同的事情,把一种方法定义为另一种方法是没有意义的。 这样做的原因是,框架可以打印出所做的比较。 为了做到这一点,它需要捕捉重载操作符被使用的上下文。

有一些非常成熟的惯例,其中(a == b)(a != b)都是假的而不一定是对立的。 特别是,在SQL中,任何与NULL的比较都会产生NULL,而不是真或假。

如果可能的话,创build这个新的例子可能不是一个好主意,因为它是非常不直观的,但是如果你试图对现有的约定build模,那么可以select让你的操作符“正确”上下文。

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

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

为什么允许开发人员超负荷的原因之一就是性能。 您可以通过实现==!=来实现优化。 那么x != y可能比!(x == y)便宜。 一些编译器可能能够为你优化它,但也许不是,特别是如果你有复杂的对象涉及大量的分支。

即使在Haskell中,开发人员非常重视法律和math概念,仍然可以重载==/= ,正如您在这里所看到的( 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' 

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

有没有什么情况可以提出关于两个对象相同的问题是有道理的,但是询问他们是不是平等的没有意义? (无论是从用户的angular度,还是从实施者的angular度来看)

这是一个意见。 也许它没有。 但是,语言devise者并不是无所不知的,他们决定不限制那些可能提出情况的人(至less对他们来说)。

回应编辑;

也就是说,如果某种types的操作符有可能是==而不是!= ,反之亦然,那么何时才有意义。

一般来说 ,不,这是没有意义的。 平等和关系经营者一般都会成立。 如果存在平等,那么不平等也是如此; 小于,然后大于等等。类似的方法也适用于算术运算符,它们也一般都是自然的逻辑集合。

这在std::rel_ops命名空间中得到了certificate。 如果你实现了相等和小于运算符,那么使用这个名字空间就可以为你提供其他的运算符,用你原来实现的运算符来实现。

这一切都说, 有没有条件或情况下,一个不会立即意味着另一个,或不能在其他方面实施? 是的 ,可以说很less,但他们在那里; 再一次, rel_ops就是它自己的名字空间。 出于这个原因,允许它们独立实现,可以让你利用语言来获得你所需要或者需要的语义,对于代码的用户或者客户来说,这种方式仍然是自然而直观的。

已经提到的懒惰评估就是一个很好的例子。 另一个很好的例子就是给它们语义,不意味着平等或者平等。 一个类似的例子是用于stream插入和提取的移位运算符<<>> 。 虽然它可能会在普遍的圈子里皱起眉头,但在某些领域特定的领域,它可能是有道理的。

如果==!=运算符实际上并不意味着相等,则与<<>>stream运算符不一样意味着位移。 如果你把这些符号当作是一些其他的概念,那么它们就不必相互排斥。

就平等而言,如果你的用例保证把对象看作是不可比较的,那么每一个比较都应该返回false(或者是一个非可比较的结果types,如果你的操作符返回非bool),那么这是有意义的。 我想不出在什么情况下需要这样做,但我可以看到这是足够合理的。

强大的力量来自负责任地,或至less是非常好的风格指南。

==!=可以被重载来做任何你想要的。 这既是祝福也是诅咒。 不能保证!=意思!(a==b)

 enum BoolPlus { kFalse = 0, kTrue = 1, kFileNotFound = -1 } BoolPlus operator==(File& other); BoolPlus operator!=(File& other); 

我不能certificate这个操作符重载,但在上面的例子中,不可能将operator!=定义为operator==的“相反”。

最后,你用这些运算符检查的是expression式a == b或a!= b返回一个布尔值。 这些expression式在比较之后返回一个布尔值,而不是相互排斥。

为什么需要两个单独的定义?

需要考虑的一件事情是,可能有更有效地实施其中一个运营商的可能性,而不仅仅是使用另一个运营商的否定。

(这里我的例子是垃圾,但是问题仍然存在,比如说bloom滤波器:如果某个东西不在一个集合中,它们允许快速testing,但是testing它是否需要更多的时间。

按照定义, a != b!(a == b)

作为程序员,这是你的责任。 写testing可能是一件好事。

也许一个无法比拟的规则,其中一个!= b是错误的,而一个== b像是一个无状态的错误。

 if( !(a == b || a != b) ){ // Stateless } 

通过定制操作员的行为,你可以让他们做你想做的事情。

你可能希望定制的东西。 例如,你可能希望自定义一个类。 这个类的对象可以通过检查一个特定的属性来比较。 知道这是事实,你可以编写一些特定的代码,只检查最小的东西,而不是检查整个对象中每一个属性的每一个位。

想象一下,你可以发现,即使不是更快,也可以发现事物的不同速度比发现事物的速度要快。 当然,一旦你弄清楚是否有相同或不同的东西,那么你可以简单地通过翻转一下来了解相反的情况。 然而,翻转这一点是一个额外的操作。 在某些情况下,当代码重新执行很多时,保存一个操作(乘以很多次)可以使整体速度增加。 (例如,如果每像素百万像素屏幕保存一个操作,那么您就可以节省一百万次操作,每秒可以多达60个屏幕,节省更多的操作。

hvd的答案提供了一些额外的例子。

是的,因为一个意思是“等同”,另一个意思是“非等价”,这个术语是相互排斥的。 这个操作员的任何其他含义都是令人困惑的,应该尽一切可能避免。