std :: map键的要求(设计决策)

时间:2012-02-23 13:11:55

标签: c++ stl map user-defined-types

当我制作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==

5 个答案:

答案 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==拖入其中的另一个原因。假设KeyTypestd::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),其中k1k2K 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<或自定义函数)。您的建议既需要执行相等比较的额外工作,也需要额外限制要求等效对象进行比较。