4.8.5 相等与等价
相等(equality)与等价(equivalent)是两个极易被混淆的概念。一个简单快速的解释是:相等基于操作符==,即x==y;而等价基于<,即!(x<y)&&!(x>y),两者在语义上有很大差别。
对于简单类型(如int),相等和等价两者是一致的,例如5==10/2和!(5<10/2)&&! (5>10/2)。但对于大多数复杂类型和自定义类型,由于==和<操作符是两个不同的运算,比较原则可能不同,从而两者具有不同的意义。
之前的point类是一个很好的例子。p1(1,2,3)和p3(3,2,1)两者完全不相等,但等价,因为等价运算使用的是operator<,它比较依据的是成员变量的平方和:1+2*2+3*3 == 3*3+2*2+1。
operators库使用equality_comparable和equivalent明确地区分了相等与等价这两个概念。equality_comparable基于==,equivalent则基于<。但让人困扰的是它们最终都提供了操作符==,表现相同但含义非常不同。
了解相等与等价的区别非常重要,特别是当自定义类被用作容器的元素的时候。标准库中的关联容器(set、map)和排序算法使用的是等价关系的<操作符,而各种查找算法find使用的是相等关系的==操作符。
point类使用不同的规则定义了==和<,可以获得正确的比较和相等语义,下面是应用于标准容器的示范代码:
- point p0, p1(1,2,3), p2(5,6,7), p3(3,2,1);
-
- using namespace boost::assign;
- vector<point> v = (list_of(p0), p1, p2, p3);
-
- BOOST_AUTO(pos, std::find(v.begin(), v.end(), point(1,2,3))); //使用相等语义查找元素
- for (; pos != v.end(); //查找下一个相等的元素
- pos = std::find(pos + 1, v.end(), point(1,2,3)))
- { pos->print(); //1, 2, 3 }
-
- pos = std::find(v.begin(), v.end(), point(2,1,3));
- assert(pos == v.end());
这段代码将只找到p1(1,2,3),并且最后一个assert断言成立,找不到值为(2,1,3)的point对象。
如果我们改变point的定义,不使用equality_comparable,而改用equivalent来实现==操作符(等价语义),那么我们不必单独定义==操作符,它将由equivalent自动用<操作符来实现。上面的测试代码的行为将完全不同,它会输出两个点:p1(1,2,3)和p3(3,2,1),并且断言失败。
在使用关联容器set和map时更需要留意,它们仅要求<操作符,因此是基于等价语义的,即使使用equality_comparable定义了==操作符,把point对象放入set或map也会产生equivalent的效果。
请读者谨慎地考虑自定义类需要什么样的语义,如果只关心类的等价语义,那么就用equivalent,如果想要精确地比较两个对象的值,就使用equality_comparable。