同时迭代和修改unordered_set?

时间:2012-12-20 22:58:45

标签: c++ c++11

请考虑以下代码:

unordered_set<T> S = ...;

for (const auto& x : S)
   if (...)
       S.insert(...);

这是正确的吗?如果我们在S中插入一些东西,那么迭代器可能会失效(由于重新散列),这将打破范围 - 因为它在使用S.begin ... S.end。

是否有一些模式可以解决这个问题?

一种方法是:

unordered_set<T> S = ...;

vector<T> S2;

for (const auto& x : S)
   if (...)
       S2.emplace_back(...);

for (auto& x : S2)
    S.insert(move(x));

这看起来很笨重。有没有更好的方法我错过了?

(特别是如果我使用的是手动哈希表,并且我可以阻止它重新散列直到循环结束,那么使用第一个版本是安全的。)

更新

来自http://en.cppreference.com/w/cpp/container/unordered_map/insert

  

如果由于插入而发生重新散列,则所有迭代器都将失效。否则迭代器不会受到影响。引用不会失效。仅当新元素数高于max_load_factor() * bucket_count()时才会发生重新散列。

你能以某种方式混淆max_load_factor以阻止重复播放吗?

3 个答案:

答案 0 :(得分:22)

  

你能不能以某种方式搞乱max_load_factor以阻止重复播放?

是的,您可以将max_load_factor()设置为无穷大,以确保不会发生重新散列:

#include <iostream>
#include <limits>
#include <unordered_set>

int main()
{
    // initialize
    std::unordered_set<int> S;

    for (int i = 0; i < 8; ++i)
        S.insert(i);

    std::cout << "buckets: " << S.bucket_count() << std::endl;

    // infinite max load factor => never need to rehash
    const auto oldLoadFactor = S.max_load_factor();
    S.max_load_factor(std::numeric_limits<float>::infinity());

    for (const auto& x : S)
    {
        if (x > 2)
            S.insert(x * 2);
    }

    // restore load factor, verify same bucket count
    S.max_load_factor(oldLoadFactor);
    std::cout << "buckets: " << S.bucket_count() << std::endl;

    // now force rehash
    S.rehash(0);
    std::cout << "buckets: " << S.bucket_count() << std::endl;
}

请注意,简单地设置新的加载因子不会重复,因此这些都是便宜的操作。

rehash(0)位有效,因为它要求:1)我至少得到 n 桶,2)我有足够的桶来满足我的{{1} }。我们只是使用零表示我们不在乎最低金额,我们只是想重新考虑以满足我们的&#34;新的&#34;因素,好像从未改变为无穷大。

当然,这不是例外安全的;如果在max_load_factor()的调用之间抛出任何东西,我们的旧因素将永远消失。使用您喜欢的范围保护实用程序或实用程序类轻松修复。

请注意,如果您要迭代新元素,则无法保证。您将迭代现有元素,但您可能会也可能不会迭代新元素。如果可以(我们的聊天应该是这样),那么这将有效。

例如,考虑迭代一组无序整数,对于每个偶数整数max_load_factor(),插入x。如果那些总是在你的位置之后插入(通过实现细节和容器状态),你将永远不会终止循环,除非通过例外。

如果您确实需要一些保证,则需要使用备用存储解决方案。

答案 1 :(得分:5)

在迭代时修改任何容器往往会变得毛茸茸 - 即使它是一个比哈希更简单的结构,或者即使你可以阻止它重新散列,重新平衡或其他什么。

顺便说一下,即使 工作,也存在歧义:你新插入的成员是否应该被迭代?是否可以将它们包含在此迭代中有时(即,只有当它们碰巧在当前迭代器之后结束时)?

如果你需要做很多事情,你可以将容器包装在一个通用适配器中,该适配器将所有插件推迟到最后,但你真的找到了隐藏已有代码的方法。

答案 2 :(得分:2)

我意识到它在概念上与你提出的相同,但我认为它看起来实际上相当光滑:

std::vector<T> tmp;
std::copy_if(S.begin(), S.end(), std::back_inserter(tmp),
             [](T const& value) { return ...; });
S.insert(std::make_move_iterator(tmp.begin()),
         std::make_move_iterator(tmp.end()));