我想知道为什么std::map
和std::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
中有两个对象,其中包含密钥key1
和key2
。现在我们要插入另一个带有键key3
的对象。
使用std::less
时,insert
功能需要先使用std::less::operator()
和key1
呼叫key3
。假设std::less::operator()(key1, key3)
返回false。必须使用切换后的密钥std::less::operator()
再次致电std::less::operator()(key3, key1)
,以确定key1
是否等于key3
或key3
是否大于key1
}。如果第一个调用返回false,则有std::less::operator()
两个调用来做出决定。
如果std::map::insert
使用compare
,就会有足够的信息只使用一次调用做出正确的决定。
根据地图中键的类型,std::less::operator()(key1, key2)
可能很昂贵。
除非我遗漏一些非常基本的内容,否则std::map
和std::set
不应使用compare
而不是std::less
作为比较密钥的默认函子?
答案 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
写入权限
地图和集合的插入点完全由单个二进制搜索确定,例如lower_bound
或upper_bound
。二进制搜索的运行时复杂性为O(log n)
读取权限
这同样适用于搜索:搜索比线性相等扫描更有效,正是因为大多数元素 不 需要进行比较。诀窍是订购了容器。
结果是equality
信息不需要存在。只是,这些项目可以具有等效排序。
实际上,这只是意味着对元素类型的约束更少,在常见使用场景中实现需求的工作量更少,性能更佳。总会有权衡取舍。 (例如,对于大型集合,散列表(无序集和映射)通常更有效。请注意,这些执行需要equatable
元素,并且它们使用了用于快速查找的哈希方案)