请考虑以下代码:
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
以阻止重复播放吗?
答案 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()));