当我制作std::map<my_data_type, mapped_value>
时,C ++对我的期望是my_data_type
有自己的operator<
。
struct my_data_type
{
my_data_type(int i) : my_i(i) { }
bool operator<(const my_data_type& other) const { return my_i < other.my_i; }
int my_i;
};
原因是您可以从operator>
派生operator==
和operator<
。 b&lt; a 暗示 a&gt; b ,所以有operator>
。 !(a&lt; b)&amp;&amp; !(b&lt; a)表示 a 既不小于 b 也不大于它,所以它们必须相等。
问题是:为什么C ++设计人员不需要明确定义operator==
?显然,operator==
对于std::map::find()
和从std::map
删除重复项是不可避免的。为什么要实现5次操作并调用方法两次,以免迫使我明确实现operator==
?
答案 0 :(得分:17)
对operator==
来说,
std::map::find()
是不可避免的
这是你出错的地方。 map
根本不使用operator==
,它不是“不可避免的”。如果x
,则y
和!(x < y) && !(y < x)
两个键被认为与地图相同。
map
不知道或不关心您是否已实施operator==
。即使你有,也不一定要按照operator==
的顺序,所有等效键都是相同的。
所有这一切的原因在于,无论C ++依赖于订单(排序,地图,集合,二进制搜索),它都将其所做的一切都基于“严格弱顺序”的易于理解的数学概念,这也是定义的在标准中。 operator==
并不特别需要,如果你看一下这些标准函数的代码,你就不会经常看到像if (!(x < y) && !(y < x))
那样的两个测试都紧密相连的东西。
此外,这些都不一定基于operator<
。 map
的默认比较器为std::less<KeyType>
,默认情况下使用operator<
。但如果您对std::less
专门KeyType
,则无需定义operator<
,如果您为地图指定了不同的比较器,那么它可能与也可能没有任何关系operator<
或std::less<KeyType>
。所以我上面说过x < y
,真的是cmp(x,y)
,其中cmp
是严格的弱顺序。
这种灵活性是不将operator==
拖入其中的另一个原因。假设KeyType
是std::string
,并且您指定自己的比较器,该比较器实现某种特定于语言环境,不区分大小写的排序规则。如果map
在某些时候使用了operator==
,那么这将完全忽略这样一个事实,即只根据大小写区别的字符串应该被视为相同的密钥(或者在某些语言中:与其他不同的区别被认为不是用于整理目的)。因此,等式比较也必须是可配置的,但程序员只能提供一个“正确”的答案。这不是一个好的情况,你永远不希望你的API提供看似自定义的东西,但实际上并非如此。
此外,这个概念是,一旦你排除了树的那个小于你正在搜索的键的部分,以及那个键小于它的树的部分,剩下的就是空(找不到匹配项)或者其中有一个键(找到匹配项)。所以,您已经使用current < key
然后使用key < current
,除了等效之外别无选择。情况正是如此:
if (search_key < current_element)
go_left();
else if (current_element < search_key)
go_right();
else
declare_equivalent();
你的建议是:
if (search_key < current_element)
go_left();
else if (current_element < search_key)
go_right();
else if (current_element == search_key)
declare_equivalent();
显然不需要。事实上,你的建议效率较低!
答案 1 :(得分:3)
您的假设不正确。这是真正发生的事情:
std::map
是一个类模板,它接受四个模板参数:键类型K
,映射类型T
,比较器Comp
和分配器Alloc
(名称)当然,这是非常重要的,只有本地的答案)。对于此讨论而言重要的是,可以使用两个关键引用来调用对象Comp comp;
,comp(k1, k2)
,其中k1
和k2
为K const &
,结果是一个布尔值,它实现了严格的弱排序。
如果未指定第三个参数,则Comp
是默认类型std::less<K>
,此(无状态)类将二进制操作作为k1 < k2
。这个<
- 运算符是K
的成员,还是自由函数,模板或其他任何内容都无关紧要。
这也包含了故事。比较器类型是实现有序映射所需的唯一数据。等式定义为!comp(a, b) && !comp(b,a)
,并且地图仅根据此相等定义存储一个唯一键。
没有理由对密钥类型提出额外要求,也没有理由认为用户定义的operator==
和operator<
应该兼容。它们既可以独立存在,也可以完全不同且不相关。
一个好的库强加了最低限度的必要要求,并提供了最大的灵活性,这正是std::map
所做的。
答案 2 :(得分:2)
为了在地图中找到元素i
,我们遍历到元素e
,树搜索已经测试i < e
,这将返回false。
因此,要么致电i == e
,要么致电e < i
,鉴于已经在树中找到e
的先决条件,两者都意味着同样的事情。由于我们必须拥有operator<
,因此我们不依赖operator==
,因为这会增加关键概念的要求。
答案 3 :(得分:1)
需要比较运算符的原因是地图的实现方式:作为binary search tree,它允许您在O(log n)
中查找,插入和删除元素。要构建此树,必须为该组键定义strict weak order。这就是为什么只需要一个运算符定义的原因。
答案 4 :(得分:1)
你有一个错误的假设:
!(a < b) && !(b < a)
意味着a既不小于b也不大于b,所以它们必须相等。
这意味着它们等效,但不一定相等。您可以自由地实现operator<
和operator==
,使两个对象可以等效但不相等。
为什么C ++设计器没有明确定义
operator==
?
简化可用作键的类型的实现,并允许您对没有重载运算符的类型使用单个自定义比较器。唯一的要求是您提供定义部分排序的比较器(operator<
或自定义函数)。您的建议既需要执行相等比较的额外工作,也需要额外限制要求等效对象进行比较。