【发布时间】:2014-05-13 02:01:13
【问题描述】:
我知道如何使用std::unordered_map::emplace,但是如何使用emplace_hint? cplusplus 和 cppreference 都没有提供一组示例来说明我们如何知道将元素放在哪里。
谁能提供一些关于这方面的信息或给出一些示例/说明我们何时可能知道放置的元素应该去哪里?
【问题讨论】:
标签: c++ c++11 stl unordered-map
我知道如何使用std::unordered_map::emplace,但是如何使用emplace_hint? cplusplus 和 cppreference 都没有提供一组示例来说明我们如何知道将元素放在哪里。
谁能提供一些关于这方面的信息或给出一些示例/说明我们何时可能知道放置的元素应该去哪里?
【问题讨论】:
标签: c++ c++11 stl unordered-map
unordered_map 可能对提示有什么作用?好吧,如果迭代器使用与 emplace_hint 被要求插入的元素相同的键来寻址元素,那么它可能会很快失败 - 只是一个键比较,没有任何散列或在任何散列冲突元素列表中摸索桶。但是,如果键不匹配,则提示在其他方面是无用的,因为任何其他键 - 无论值多么“接近” - 应该(概率上)位于完全不相关的存储桶中(考虑到通常被认为是“好”的散列函数),所以时间会浪费在关键比较上,只能像普通的emplace一样重新开始。
当您插入预先按键排序的元素时,这可能很有用,旨在删除过程中的大量重复项,但是键太大了,将迭代器保留到刚刚插入的元素比密钥的副本,或者哈希函数可能特别慢。
unordered_map::emplace_hint 的另一个好处是与 map::emplace_hint 更好的 API 兼容性,因此代码可以切换容器类型并让 emplace_hints 不会中断编译,尽管它们最终可能比代码切换到更慢emplace() 作为对 map 有帮助的关闭但不同的键提示可能对 unordered_map 无用。
只需获取 GCC 10.2 g++ -E 的输出,看看它是否执行上述操作。 emplace_hint 调用_M_insert_multi_node(...) 其中有这行:
__node_base* __prev = __builtin_expect(__hint != nullptr, false)
&& this->_M_equals(__k, __code, __hint)
? __hint
: _M_find_before_node(__bkt, __k, __code);
上面,__k是可以插入的key,__code是哈希码,__hint是提示迭代器/指针; _M_equals(...) 返回:
return _Equal_hash_code<__node_type>::_S_equals(__c, *__n) &&
_M_eq()(__k, this->_M_extract()(__n->_M_v()));
因此,在使用提示迭代器。这是它使用提示的唯一情况。想象一下,在逻辑上,存储桶有一些碰撞元素与键 K1、K2、K3、K4 链接在一起,并且您的提示迭代器指向 K4,但您尝试使用 K2 插入重复项:因为迭代器仅向前 em>,您必须使用_M_find_before_node(...) 才能比提示指向的更早到达链中的碰撞元素。在_M_find_before_node(...) 之后,您可以从 K1 向前扫描,以查看要插入的键 - K2 - 是否已经存在于在桶中碰撞的元素中。
(当已知键比较很便宜时,可以通过跳过哈希比较来改进实现,但是使用类型特征来正确处理该条件会有点痛苦 - 你怎么知道哪些键相等函数很便宜? 对于小型、标准布局、可简单复制的类型或类似类型,至少可以假设无序容器使用默认的std::equals<> 比较来实例化......)。
【讨论】:
lower_bound搜索或生成map 的提示。
insert_or_assign 和 try_emplace 用于查找尝试之后会立即插入或更新,但是是的 - 有一些东西可以帮助您及时将两者分开可能会很好有时:最有用的事情是保留桶号并使用它来避免散列;你不能有一个特定存储桶的迭代器,并且一些存储桶缺少元素 - 所以迭代器提示很尴尬。
unordered_map 从排序的源(例如文件)填充时,我想知道整数键。我认为在这种情况下emplace_hint 没有任何优势,但我希望我错了。