我的代码中出现的最常见错误之一是在循环期间修改STL容器。
在循环执行期间删除或添加元素,因此我经常遇到超出范围的异常。
我的for循环通常如下所示:
for (auto& Item : Items) { // Will not work when Items container is modified
//... loop logic
}
当可以删除多个项目时,我会使用这个怪物:
for (int Index=Items.size()-1;Index<=0;Index--) {
if (Index<Items.size()) { //Because multiple items can be removed in a single loop
//... loop logic
}
}
这看起来很糟糕,并且使用第二个选项让我感觉很糟糕。可以删除多个项目的原因是由于事件,单个事件可以删除任意数量的元素。
这是一些伪代码,用于说明何时发生这种情况:
// for each button in vector<button> {
// process button events
// event adds more buttons to vector<button>
// *ERROR* vector<button> is modified during loop.
// }
在另一个例子中,想象一个包含以下项目的向量:
// 0 1 2 3 4 5 6 7 8 9
我们在0
开始循环,逐个元素地去。在4
,我想删除元素1
,4
和9
,因此我们无法在此处使用正常循环。
答案 0 :(得分:7)
将std::remove_if
与谓词一起使用,该谓词决定是否需要删除按钮:
bool needsRemoved(const Button& button);
vec.erase(std::remove_if(vec.begin(), vec.end(), &needsRemoved), vec.end());
编辑:对于您的上一个示例,二次(即性能不佳)算法是:
std::vector<int> vec = {0,1,2,3,4,5,6,7,8,9};
auto end = vec.end();
for (auto it = vec.begin(); it < end; ++it)
{
std::set<int> bad = {1, 4, 9};
end = std::remove_if
(vec.begin(), end,
[bad](int x) { return (bad.find(x) != bad.end()); });
}
vec.erase(end, vec.end());
使用快速查找的容器(如集合或地图)可能会更好。
答案 1 :(得分:3)
有两种方法可靠地做到这一点:
迭代原始容器的副本并操作原始容器。除非您的容器存储指针,而不是直接存储实际元素,否则这可能是不可行的。
不允许直接操作容器,而是以某种方式标记要删除的元素,并在迭代后扫描它们。您还可以通过将新元素插入单独的临时容器并在循环完成后附加到原始元素来添加新元素 - 您还可以使用已删除的元素执行此操作,从而无需在元素本身中存储“已删除”标志。当然,这可以通过适当的add
和remove
函数来抽象出来。
编辑:解决方案#2的删除部分可以通过rectummelancolique显示的删除删除习惯做得很好。
答案 2 :(得分:0)
由于有按钮(我希望没有太多),你可能想要为每个按钮添加一个标志,告诉它是否已完全处理或类似的事情。然后,您查找数组中尚未处理的第一个项目并进行处理。重复此操作直到所有项目都已处理完毕。
for (;;) // breaks, when all items have been processed.
{
auto it = std::find( std::begin(Items), std::end(Items),
[](const Item & item){ return item.hasBeenProcessed(); }
if ( it == std::end(Items) )
break;
process( *it );
}
这应该是安全的。注意,这可以具有关于项目数量的二次时间复杂度。正如我所说,希望不会有太多项目。如果这是一个问题,您可能需要稍微优化此循环,例如,开始上次离开时的搜索。但只有当它成为问题时才这样做。
答案 3 :(得分:0)
因为你在谈论按钮和按钮事件: 最简单的解决方案就是将循环重置为开始时 你处理一个事件:
for ( auto current = items.begin(); current != items.end(); ++ current ) {
if ( current->hasEventWhichNeedsProcessing() ) {
current->processEvent(); // possibly invalidates current
current = items.begin(); // revalidates current
}
}
如果我们正在谈论按钮事件(由于 一个人类用户行动),这应该是安全的,因为你应该 通常能够在新事件之前处理所有事件 发生。 (对于非常迅速的事件,你可能永远不会到达 最后的条目。)
但是,我仍然不确定这是最好的解决方案。 无论你如何迭代,它都意味着你可以对待 事件的顺序与他们到达的顺序不同。更好的解决方案 将事件本身推送到列表上,然后 按顺序处理此列表(作为队列)。