C++ 同时迭代和修改无序的_集?

C++ 同时迭代和修改无序的_集?,c++,c++11,C++,C++11,考虑以下代码: unordered_set<T> S = ...; for (const auto& x : S) if (...) S.insert(...); unordered_集S=。。。; 用于(常量自动和x:S) 如果(…) S.插入(…); 这是坏的,对吗?如果我们在S中插入了一些东西,那么迭代器可能会失效(由于重新刷新),这将打破范围,因为在引擎盖下它正在使用S.begin。。。结束 有什么模式可以处理这个问题吗 一种方法是: uno

考虑以下代码:

unordered_set<T> S = ...;

for (const auto& x : S)
   if (...)
       S.insert(...);
unordered_集S=。。。;
用于(常量自动和x:S)
如果(…)
S.插入(…);
这是坏的,对吗?如果我们在S中插入了一些东西,那么迭代器可能会失效(由于重新刷新),这将打破范围,因为在引擎盖下它正在使用S.begin。。。结束

有什么模式可以处理这个问题吗

一种方法是:

unordered_set<T> S = ...;

vector<T> S2;

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

for (auto& x : S2)
    S.insert(move(x));
unordered_集S=。。。;
向量S2;
用于(常量自动和x:S)
如果(…)
S2.向后安放(…);
用于(自动和x:S2)
S.插入(移动(x));
这看起来很笨重。有没有更好的方法让我错过

(特别是如果我使用的是一个手动滚动的哈希表,并且我可以阻止它在循环结束之前进行重新灰化,那么使用第一个版本是安全的。)

更新:

如果由于插入而发生重新灰化,则所有迭代器都将无效。否则迭代器不受影响。引用不会失效。仅当新的元素数高于
max\u load\u factor()*bucket\u count()
时,才会进行重新灰化


你能不能以某种方式弄乱
max\u load\u factor
以防止重新哈希?

当你对任何容器进行迭代时,修改它往往会让人毛骨悚然——即使它的结构比哈希更简单,或者即使你可以防止它重新哈希、重新平衡或其他

顺便说一句,即使它真的起作用了,也有一个模棱两可的问题:是否应该迭代新插入的成员?有时将它们包含在这个迭代中可以吗(即,仅当它们恰好在当前迭代器之后结束)


如果您需要经常这样做,您可以将容器包装在一个通用适配器中,将所有插入延迟到最后,但您确实找到了一种隐藏已有代码的方法。

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

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()));
std::vector-tmp;
std::copy_if(S.begin()、S.end()、std::back_插入器(tmp),
[](T const&value){return…;});
S.insert(std::make_move_迭代器(tmp.begin()),
std::make_move_迭代器(tmp.end());
你能以某种方式改变最大负荷系数来防止再灰化吗

是的,您可以将
max\u load\u 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;
}
#包括
#包括
#包括
int main()
{
//初始化
std::无序的_集;
对于(int i=0;i<8;++i)
S.插入(i);

std::cout我相信你可能是对的,但是你可以使用
迭代器
插入重载来摆脱第二个循环。
s.insert(x.begin(),x.end());
(如果T不是POD,你可以使用
std::make\u move\u迭代器
s.insert(std::make\u move\u迭代器(x.begin()),std::make\u move\u迭代器(x.end());
更详细,但我还是更喜欢it@Andrew&111111:我不知道需要多少重复才能看到已删除的答案,但如果你不能:你是对的!我确实认为在这里使用基于范围的for循环是一种转移视线的方法。@GManNickG:range-based for基本上等同于普通for over begin…end迭代器range:@AndrewTomazos-Fathomling:,但正如我提到的,您失去了对迭代器的控制,这在您修改容器时非常重要(无论是
std::vector
std::set
,等等),因为您需要更新迭代器以使其再次有效。我认为,您的问题更侧重于容器的独特迭代属性,而不是迭代方法。:)事实上,我认为简单命令式样式更优雅、可读性更高,并且更易于理解。如果我的维护程序员看到你在这里写道:)但你是对的,两人都将编译成相同的代码,并进行不必要的移动。事实上,你的代码有一个额外的移动(lamdba return to push_back(&&&)而不是emplace),但很可能会被rvo忽略。我认为我没有额外的移动,因为lambda返回值是谓词的布尔值,映射的元素不会移动,但无论如何都会被复制:因为类型相同,所以不会创建任何临时的。也就是说,我认为描述性编程风格要优越得多。我t可能不熟悉,但它实际上表达了S的作用,而不是让读者想知道循环的作用。哦,我明白了,在这种情况下,您的代码是错误的。对不起。在我的版本中,新插入的项是一个任意表达式。在您的版本中,您将S的一些子集添加回S,这将始终是一个不可操作的,没有任何意义。请参见w邪恶的函数式风格让你(和我)感到困惑:)你应该改成命令式。:)不,我的代码表达了我从命令式编程中理解的东西!:-…但你是对的,这没有意义,也没有
std::transform\u if()
这将根据条件添加对象并对其进行转换。设置较大的最大负载因子会增加哈希冲突的可能性,这会显著降低性能。我宁愿保留存储桶的数量。它通过将性能损失换取更多内存使用来实现同一目标。是否可以保证我将迭代所有现有元素(循环之前存在的元素)元素?