编辑:我有很多答案告诉我,我应该将删除分成另一个循环。也许我没有说清楚,但我在上一段中说过,我想找到一个解决方案,而不是那个。即保持当前的代码结构,但使用一些鲜为人知的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()
如果 x()
返回true,因为当它执行时,Child将告诉Parent将其从向量中移除,使迭代器无效。
除了制作之外,还有什么方法可以解决这个问题 Parent::erase()
将迭代器一直传递回去 Parent::update()
?这将是有问题的,因为每次调用都没有调用它 Child::update()
因此,该函数需要一种方法来每隔一次将迭代器返回给自己,并且它当前还返回另一个值。我还希望避免使用其他类似方法将擦除过程与更新循环分开。
你不能真正迭代并同时改变一个std :: vector,除非在迭代之间有一些通信,即变异。
我已经看到其他非标准容器通过“智能”迭代器来实现,这些迭代器知道它们的值何时被删除(并且可能自动跳转到下一个项目)。虽然它更加保持书籍。
我建议您重新构建代码,以便不混合两种不同的更新操作(通过删除某些元素)和聚合(通过累加值)数据。
你可以通过改变的返回值来做到这一点 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
,但我不确定是否可以将它与修改序列中对象的谓词一起使用。
如果你可以通过更新函数传递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;
}
}
}
如何将要删除的子项添加到列表中,并在更新每个子项后删除它们。
实际上,您将推迟删除直到第一个循环之后。
我不确定你所有的设计约束/目标,但是你可以通过它的公共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
。
解决方案的一个基础(正如其他人所说)是从std :: vector切换到std :: list,因为您可以从列表中擦除节点而不会使对其他节点的引用无效。
但是由于更好,矢量往往比列表具有更好的性能 参考地点 并且还增加了大量的内存开销(在每个节点的prev和next指针中,但也以系统分配器中每个已分配块的开销的形式)。
然后,我做了一些类似的要求,就是坚持使用矢量,但是当元素被删除时,允许在矢量中使用空洞或“死项”。
在顺序迭代期间,您需要能够检测并跳过向量中的死项,但是您可以折叠向量以定期删除这些死项(或者在删除元素已完成的特定迭代循环之后)。您还可以(如有必要)包含一个空闲列表,以便为新孩子重复使用死亡条目。
我在以下博文中更详细地描述了这个设置::
http://upcoder.com/12/vector-hosted-lists/
我在博客文章中谈到的其他一些可能与这些要求相关的设置是“矢量托管”链表和带有自定义内存池的链表。
尝试下面的修改版本:
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();
我认为问题是,你的方法“更新”也会使std :: vector :: erase的方式与迭代器无效。所以我建议你做完全相同的事情:返回新的,有效的迭代器并相应地apdapt调用循环。