编辑:我有很多答案告诉我应该将删除分成另一个循环。也许我没有说清楚,但我在上一段中说过,我想找到一个解决方案,而不是那个。即保持当前的代码结构,但使用一些鲜为人知的C ++ fu来使其工作。
好吧,我知道在向量上调用erase()
会使元素的迭代器和它之后的所有迭代器失效,并且erase()
会将迭代器返回到下一个有效的迭代器,但是如果擦除发生了怎么办?别处?
我有以下情况(简化):
警告:不要认为这是整个代码。下面显示的内容非常简单,以说明我的问题。下面显示的所有类和方法实际上都要复杂得多。
class Child {
Parent *parent;
}
class Parent {
vector<Child*> child;
}
void Parent::erase(Child* a) {
// find an iterator, it, that points to Child* a
child.erase(it);
}
int Child::update() {
if(x()) parent.erase(*this) // Sometimes it will; sometimes (most) it won't
return y;
}
void Parent::update() {
int i = 0;
for(vector<A>::iterator it = child.begin(); it != child.end(); it++)
i += (*it)->update();
}
所以,很明显,如果(*it)->update()
返回true,它会在运行x()
后崩溃,因为当它发生时,它会告诉Parent将它从向量中移除,使迭代器无效。 / p>
除了让Parent::erase()
将迭代器一直传递回Parent::update()
之外,还有什么方法可以解决这个问题吗?这将是有问题的,因为每次调用Child::update()
都不会调用它,因此该函数需要一种方法来每隔一次将迭代器返回给自己,并且它当前还返回另一个值。我还希望避免使用其他类似方法将擦除过程与更新循环分开。
答案 0 :(得分:4)
我建议您重新构建代码,以便不混合两种不同的更新操作(通过删除某些元素)和聚合(通过添加值)数据。
您可以将Child::update
的返回值更改为std::pair<int, bool>
,其中int
是值,bool
表示此元素是否应该被删除。
如果您可以使Child::update
成为const
方法(意味着它不会修改对象,只调用其他const
方法),您可以编写一个可以使用的简单仿函数与std::remove_if
。像这样:
class update_delete {
public:
update_delete() : sum(0) {}
bool operator()(const Child & child) {
std::pair<int, bool> result = child.update();
sum += result.first;
return result.second;
}
private:
int sum;
}
如果你不能使它update
const
,只需将元素与后面的某个元素交换(你必须保留一个迭代器,它始终指向可用于交换的最后一个元素) 。完成聚合后,只需使用vector::resize
丢弃向量的末尾(现在包含要删除的所有元素)。这类似于使用std::remove_if
,但我不确定是否可以将它与修改序列中对象的谓词一起使用。
答案 1 :(得分:3)
如果您可以同时通知更新功能中的erase-intent和id,那么您可以这样做:
int Child::update(bool& erase) {
erase = x();
return y;
}
void Parent::update() {
int i = 0;
for(vector<A>::iterator it = child.begin(); it != child.end();) {
bool erase = false;
i += (*it)->update(erase);
if (erase) {
it = child.erase(it); // erase returns next iterator
} else {
++it;
}
}
}
答案 2 :(得分:3)
你不能真正迭代并同时改变std :: vector,除非迭代之间存在一些通信变异。
我已经看到其他非标准容器通过“智能”迭代器来实现这一点,这些迭代器知道它们的值何时被删除(并且可能会自动跳转到下一个项目)。不过,它的预订相当多。
答案 3 :(得分:2)
如何将要删除的子项添加到列表中,并在更新每个子项后删除它们。
实际上,您将推迟删除,直到第一次循环之后。
答案 4 :(得分:1)
我不确定您的所有设计约束/目标,但是您可以通过它的公共API公开删除子项的必要性,只需让Parent::update
有条件地执行删除。
void Parent::update()
{
int i = 0;
for( vector<A>::iterator it = child.begin();
it != child.end(); /* ++it done conditionally below */ )
{
i += (*it)->update();
if( (*it)->should_erase() )
{
it = child.erase( it );
}
else
{
++it;
}
}
}
bool Child::should_erase() const
{
return x();
}
int Child::update()
{
// Possibly do other work here.
return y;
}
然后你可以删除Parent::erase
。
答案 5 :(得分:1)
解决方案的一个基础(正如其他人所说)是从std :: vector切换到std :: list,因为您可以从列表中擦除节点而不会使对其他节点的引用无效。
但是矢量往往比列表具有更好的性能,因为更好的引用位置并且还增加了大量的内存开销(在每个节点的prev和next指针中,但也在系统分配器中每个已分配块的开销形式。)
然后,我做了一些类似的要求,就是坚持使用矢量,但是要允许空洞或“死亡条目”。在元素被删除时在向量中。
您需要能够在顺序迭代期间检测并跳过向量中的死项,但是您可以折叠向量以定期删除这些死项(或者在删除元素的特定迭代循环之后)已完成)。您还可以(如有必要)包含一个空闲列表,以便为新孩子重复使用死亡条目。
我在以下博文中更详细地描述了这个设置:: http://upcoder.com/12/vector-hosted-lists/
我在博客文章中谈到的其他几个可能与这些要求相关的设置是“托管的”矢量&#39;链表和带有自定义内存池的链表。
答案 6 :(得分:0)
尝试以下修改后的版本:
for(vector<A>::iterator it = child.begin(); it != child.end();)
i += (*it++)->update();
编辑:这当然是非常糟糕的,但如果你可以将容器更改为std::list
,可以正常工作。如果没有这种改变,你可以尝试一个反向迭代器(如果顺序无关紧要),即
for(vector<A>::reverse_iterator it = child.rbegin(); it != child.rend();)
i += (*it++)->update();
答案 7 :(得分:0)
我认为问题是,你的方法“update”也会以与std :: vector :: erase完全相同的方式使迭代器失效。所以我建议你做同样的事情:返回新的,有效的迭代器并相应地apdapt调用循环。