【发布时间】:2021-10-13 18:36:13
【问题描述】:
使用 C++17 node-extract() 函数,我可以更改密钥,而无需重新分配节点。在我的特定用例中,我将键替换为相等的键,因此我想使用 insert()-with-hint 来避免完整的第二次查找,并且在使用 std::map:
struct Replacement { std::string from, to; };
std::map<std::string_view, Replacement> replacements;
~~~~
std::string from = ~~~;
std::string to = ~~~:
auto [it, inserted] = replacements.try_emplace(from, std::move(from), std::move(to));
if (inserted) {
// need to patch up the key, which points to invalid data now:
auto node = replacements.extract(it++); // 1
node.key() = node.mapped().from; // 2
replacements.insert(it, std::move(node)); // 3
}
当我insert() 是一个新节点时,它的key_type 将继续指向from.data(),但是,作为mapped_type 构造的一部分,它会被移出。由于 SSO(小字符串优化),移动字符串的 data() 可能与源字符串的 data() 不同,一旦 from 的生命周期结束,key_type 就会失效,可能更早。所以我们需要重新设置key_type指向mapped_type,为此我们(1)提取节点,确保it在extract()之前递增保持有效,然后(2)根据需要修补密钥,最后 (3) 在旧位置重新插入节点,由提示 it 给出。
到目前为止一切顺利。现在用unordered_map试试同样的方法:
~~~~
std::string from = ~~~;
std::string to = ~~~:
auto [it, inserted] = replacements.try_emplace(from, std::move(from), std::move(to));
if (inserted) {
// need to patch up the key, which points to invalid data now:
auto node = replacements.extract(it++); // only thing possible, but wrong direction
// auto node = replacements.extract(it--); // right direction, but ERROR: unordered_map isn't bidirectional
node.key() = node.mapped().from; // 2
replacements.insert(it, std::move(node)); // 3
}
在这里,我遇到了问题,unordered_map,作为vector<forward_list>,只有前向迭代器,所以我不能,因为我不得不,向后记住最终插入的确切插入点-hint (3) 按迭代顺序。看来我只有两个选择:
- 在没有提示的情况下使用 insert(node),导致完整的第二次查找
- 增加迭代器并将其作为提示传递。 std 表示插入是尽可能接近提示执行的,但是如果我的哈希表与良好性能所需的一样稀疏,则至少使用桶内迭代器是仅向前的,则递增的迭代器可能在桶列表中排几个桶,使提示无用。
所以,好吧,这个提示对unordered_map 没用。为了保存问题,我们来谈谈unordered_multimap,然后:)
在unordered_map 中,提示可能很有用,因为与unordered_map 不同,unordered_multimap 需要扫描存储桶以将新节点放入密钥的潜在equal_range() .有没有比后增量或根本不产生提示更好的方法?
【问题讨论】:
-
我只想说:如果扫描存储桶是一个性能问题,那么您应该考虑使用常规的
map,但是谁不喜欢一个好的“Gedankenexperiment”呢? -
是否将
from和std::move(from)都传递给同一个函数调用定义的行为? -
使用地图,完整查找需要 O(log n) 时间,提供插入提示可以将其减少到 O(1)。使用无序地图,即使进行完整查找,您也有 O(1) 时间。将提示插入未绘制的地图是否使其在实践中更有效是值得怀疑的。一些基准测试可能有用。
-
顺便说一句,使用地图,提示是“迭代器到将插入新元素的位置”。你不应该增加
it而不是减少吗? -
@Enlico 取决于实现细节不会使代码行为未定义。遇到未定义的行为是未定义的行为。在这种情况下,如果两个实现细节都对我们不利,最坏的情况是存储在节点中的哈希是根据从 std::string 移出的数据计算得出的,这是臭名昭著的有效-但未指定的状态。因此,我们得到了一个节点,该节点的关键引用从数据中移出,并且哈希值是错误的。两者都通过在
extract()ing 之后和insert()ing 之前分配一个新键来修复,重新建立不变量。
标签: c++ c++17 string-view unordered-multimap insertion-with-hint