从 equal_range 查询过滤和修改 boost::multi_index 中的元素

Filter and modify elements in boost::multi_index from an equal_range query

我有一个 boost::multi_indexhashed_non_unique 视图。我想要完成的是,在那个视图中给定一个键,使用

pair<myIter, myIter> iterRange = myView.equal_range(key);
for (myIter iter = iterRange.first; iter != iterRange.second; ++iter) {
    // ...
}

查找与该键关联的所有元素。然后,运行这些元素通过过滤器

bool filter(Element e) { /* some filtering logic*/ }

并用修饰符修改过滤后的结果

void modifier(Element e) { /* modify the elements with e.g. myView.modify() */ }

但是,简单地将这些部分放在一起是行不通的,因为修改元素会导致 multi_index 重新排序,这会使我的 iterRange 无效。

执行此操作的正确方法是什么?谢谢!

我想我自己找到了答案。不是简单地修改 for 循环内的元素,我们需要先缓存元素,然后再修改它们以避免改变顺序。这里的技巧是,不是将迭代器缓存到这个特定视图,而是将迭代器缓存到元素本身,即

vector<BMIIter> iters;
for (myIter iter = iterRange.first; iter != iterRange.second; ++iter) {
    if (filter(*iter)) {
        iters.push_back(myBMI.iterator_to(*iter));
    }
}
for (auto iter : iters) {
    myBMI.modify(iter, modifier);
}

请注意,BMIItermyIter 是不同的迭代器类型 - 前者是元素本身的迭代器,而后者是特定于 myView 的迭代器。修改 multi_index 中的元素会使后者无效,但前者即使在重新排序后仍然有效。

对您提出的解决方案的一些评论:

  • BMIter 并不像您暗示的那样特别,而只是与容器的第一个索引关联的迭代器。请注意,当 myView 恰好是第一个索引时,这将与 myIter 相同。
  • 然而,哈希索引的迭代器are not invalidated by insertions or modifications,所以你是安全的。事实上,您可以将 iters 定义为 vector<myIter> 并直接存储迭代器而无需任何进一步的转换——您仍然实现了预期效果,即在修改后不受潜在重新排序的影响。
  • 即使您所做的一切都很好,但如果您想要获得一些额外的性能,请注意修改散列索引中的元素 does not change the underlying ordering when the key remains equivalent,因此重新排序可能会影响您的唯一方法当遍历等效键的范围时,修改的元素直接跳转 范围之后(即,就在 iterRange.second 之前)。有了这个想法,您就可以省下 iters 技巧,如下所示:

for (myIter iter = iterRange.first; iter != iterRange.second; ) {
    auto nextIter = std::next(iter);
    if (filter(*iter)) {
        myView.modify(iter, modifier);
        if (nextIter != iterRange.second && std::next(iter) == iterRange.second)
            iterRange.second = iter;
    }
    iter = nextIter;
}