在erase()之后保持有效的vector :: iterator

时间:2011-05-23 10:58:19

标签: c++ iterator

编辑:我有很多答案告诉我应该将删除分成另一个循环。也许我没有说清楚,但我在上一段中说过,我想找到一个解决方案,而不是那个。即保持当前的代码结构,但使用一些鲜为人知的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()都不会调用它,因此该函数需要一种方法来每隔一次将迭代器返回给自己,并且它当前还返回另一个值。我还希望避免使用其他类似方法将擦除过程与更新循环分开。

8 个答案:

答案 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调用循环。