I'm learning about operator overloading in C++, and I see that == and != are simply some special functions which can be customized for user-defined types. My concern is, though, why are there two separate definitions needed? I thought that if a == b is true, then a != b is automatically false, and vice versa, and there is no other possibility, because, by definition, a != b is !(a == b). And I couldn't imagine any situation in which this wasn't true. But perhaps my imagination is limited or I am ignorant of something?
我知道我可以用一个定义另一个,但这不是我要问的。我也没有问通过值和单位来比较对象之间的区别。或者两个对象是否可以同时相等和不相等(这绝对不是一个选项!这些事情是相互排斥的)。我想问的是:
有没有可能在这种情况下,问两个对象是否相等的问题是有意义的,但问它们不相等的问题是没有意义的?(无论是从用户的角度,还是从实现者的角度)
如果没有这种可能性,那么究竟为什么c++将这两个操作符定义为两个不同的函数呢?
当a == b返回bool类型以外的值时,您不会希望语言自动将a != b重写为!(a == b)。有几个原因可以让它这样做。
您可能有表达式构建器对象,其中a == b不会也不打算执行任何比较,而只是构建一些表示a == b的表达式节点。
你可能有惰性求值,其中a == b不会也不打算直接执行任何比较,而是返回某种惰性<bool>,可以在以后的某个时间隐式或显式地转换为bool来实际执行比较。可能与表达式构建器对象结合使用,以便在求值之前完成表达式优化。
你可能有一些自定义的可选<T>模板类,其中给定可选变量T和u,你想允许T == u,但让它返回可选<bool>。
可能还有我没想到的事。即使在这些例子中,操作a == b和a != b都是有意义的,但a != b与!(a == b)不是一回事,因此需要单独的定义。
但我担心的是,为什么需要两个不同的定义呢?
你不必同时定义两者。
如果它们是互斥的,您仍然可以通过在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';
}
有没有可能的情况是问关于二的问题
对象相等是有意义的,但是问它们不相等
相等没有意义吗?
我们经常把这些运算符和相等联系起来。
尽管这是它们在基本类型上的行为方式,但没有义务在自定义数据类型上也是如此。
如果你不想,你甚至不需要返回bool值。
我见过人们以奇怪的方式重载操作符,最后却发现这对特定领域的应用程序是有意义的。即使接口显示它们是互斥的,作者也可能希望添加特定的内部逻辑。
(无论是从用户的角度,还是从实现者的角度)
我知道你想要一个具体的例子,
下面是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 );
}
这些操作符做不同的事情,将一个方法定义为另一个方法的!(不是)是没有意义的。这样做的原因是,框架可以打印出所做的比较。为了做到这一点,它需要捕获所使用的重载操作符的上下文。
作为对编辑的回应;
也就是说,如果某些类型可能具有操作符==而没有!=,或者反之亦然,以及在什么情况下这样做有意义。
一般来说,不,这说不通。相等运算符和关系运算符通常是集合。如果存在平等,那么也存在不平等;小于,然后大于,以此类推,使用<= etc。类似的方法也应用于算术运算符,它们通常也是自然逻辑集。
这在std::rel_ops命名空间中得到了证明。如果实现了相等操作符和小于操作符,则使用该名称空间将提供其他名称,这些名称空间是根据原始实现的操作符实现的。
话虽如此,在某些条件或情况下,其中一个并不立即意味着另一个,或者不能在其他方面得到实施?是的,有,可以说很少,但它们确实存在;同样,正如rel_ops是它自己的名称空间所证明的那样。出于这个原因,允许它们独立实现可以让您利用语言来获得所需的语义,或者以一种对代码的用户或客户端来说仍然自然和直观的方式获得所需的语义。
前面提到的惰性求值就是一个很好的例子。另一个很好的例子是,给他们不等于或不等于的语义。一个类似的例子是位移位操作符<<和>>用于流插入和提取。虽然在一般的圈子里它可能会引起不满,但在某些特定领域它可能是有意义的。
我只回答你问题的第二部分,即:
如果没有这种可能性,那么究竟为什么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'
这可能被认为是微观优化,但在某些情况下可能是合理的。
通过自定义操作符的行为,您可以使它们按照您的要求进行操作。
你可能希望自定义一些东西。例如,您可能希望自定义一个类。可以通过检查特定属性来比较该类的对象。了解了这种情况后,您可以编写一些只检查最小值的特定代码,而不是检查整个对象中每个属性的每一位。
Imagine a case where you can figure out that something is different just as fast, if not faster, than you can find out something is the same. Granted, once you figure out whether something is the same or different, then you can know the opposite simply by flipping a bit. However, flipping that bit is an extra operation. In some cases, when code gets re-executed a lot, saving one operation (multiplied by many times) can have an overall speed increase. (For instance, if you save one operation per pixel of a megapixel screen, then you've just saved a million operations. Multiplied by 60 screens per second, and you save even more operations.)
Hvd的回答提供了一些额外的例子。