为什么使用std :: less作为比较std :: map和std :: set中的键的默认函子?

时间:2014-03-10 18:33:09

标签: c++ stdmap stdset

我想知道为什么std::mapstd::set使用std::less作为比较键的默认函子。为什么不使用类似于strcmp的仿函数?类似的东西:

  template <typename T> struct compare
  {
     // Return less than 0 if lhs < rhs
     // Return 0 if lhs == rhs
     // Return greater than 0 if lhs > rhs
     int operator()(T const& lhs, T const& rhs)
     {
        return (lhs-rhs);
     }
  }

假设map中有两个对象,其中包含密钥key1key2。现在我们要插入另一个带有键key3的对象。

使用std::less时,insert功能需要先使用std::less::operator()key1呼叫key3。假设std::less::operator()(key1, key3)返回false。必须使用切换后的密钥std::less::operator()再次致电std::less::operator()(key3, key1),以确定key1是否等于key3key3是否大于key1 }。如果第一个调用返回false,则有std::less::operator()两个调用来做出决定。

如果std::map::insert使用compare,就会有足够的信息只使用一次调用做出正确的决定。

根据地图中键的类型,std::less::operator()(key1, key2)可能很昂贵。

除非我遗漏一些非常基本的内容,否则std::mapstd::set不应使用compare而不是std::less作为比较密钥的默认函子?

2 个答案:

答案 0 :(得分:21)

我决定问亚历山大·斯捷潘诺夫(STL的设计师)。我允许引用他如下:

  

最初,我提出了3路比较。标准委员会问道     我要改为标准比较运算符。我做了我被告知的事情。     我一直在倡导为标准添加3路组件     超过20年。

但请注意,也许不直观,双向并不是一个巨大的开销。 您不必进行两倍的比较。在下行途中每个节点只有一次比较(没有相等检查)。成本无法提前返回(当密钥处于非叶子时)和最后一个额外的比较(交换参数以检查相等性)。如果我没有弄错的话,那就是

1 + 1/2*1 + 1/4*2 + 1/8*3 + ...
= 1 + 1/2+1/4+1/8+... + 1/4+1/8+... + ...
-> 3  (depth -> infty)

在包含查询元素的平衡树上进行额外的比较。

另一方面,三向比较没有可怕的开销:Branchless 3-way integer comparison。现在,在每个节点检查比较结果与0(相等)的额外分支是否比在最后支付~3个额外比较的开销更少是另一个问题。可能并不重要。但我认为比较本身应该是3值的,因此可以改变是否使用所有3种结果的决定。

更新:请参阅下面的评论,了解为什么我认为树的三向比较更好,但不一定是平面阵列。

答案 1 :(得分:17)

基于树的容器只需要严格的弱总排序。

请参阅https://www.sgi.com/tech/stl/StrictWeakOrdering.html

  1. 写入权限

    地图和集合的插入点完全由单个二进制搜索确定,例如lower_boundupper_bound。二进制搜索的运行时复杂性为O(log n)

  2. 读取权限

    这同样适用于搜索:搜索比线性相等扫描更有效,正是因为大多数元素 需要进行比较。诀窍是订购了容器。


  3. 结果是equality信息不需要存在。只是,这些项目可以具有等效排序

    实际上,这只是意味着对元素类型的约束更少,在常见使用场景中实现需求的工作量更少,性能更佳。总会有权衡取舍。 (例如,对于大型集合,散列表(无序集和映射)通常更有效。请注意,这些执行需要equatable元素,并且它们使用了用于快速查找的哈希方案)