【问题标题】:std containers iterator invalidation during erase [duplicate]擦除期间std容器迭代器失效[重复]
【发布时间】:2011-09-23 03:15:10
【问题描述】:

可能的重复:
vector erase iterator
Problem with std::map::iterator after calling erase()


我对我拥有的一段代码感到担忧。我有组件和一个存储组件的对象。问题是在更新期间组件可以告诉从对象中删除组件。但它是从另一个函数调用的。

void Object::update() { //using std::map here
   for(ComponentMap::iterator i = components.begin(); i != components.end(); ++i) {
      (*i).second->update();
   }
}

void HealthComponent::update() {
   if(health <= 0) object->removeComponent("AliveComponent"); //this is wrong logic. but its just an example :D
}

void Object::removeComponent(string component) {

  ComponentMap::iterator i = components.find(component);
  if(i == components.end()) return;

  components.erase(i);

}

假设我有很多组件 - Health、Alive、Graphics、Physics、Input 等。

我尝试了类似的方法(使用一些测试组件)并且在更新期间没有错误。但我真的很担心。它可以在将来弹出我的错误吗?如果是,如何解决?

提前致谢,
加西姆

【问题讨论】:

  • 在您的示例代码中,看起来您将删除一个元素,该元素不一定是 Object::update() 当前正在作用的元素。这是一个更加困难的问题,如果这确实是您想要做的事情,可能需要重新考虑您的设计。
  • @karlphillip:这不是“矢量擦除迭代器”的副本,但它可能是另一个问题的副本。正如我刚刚发现的那样,序列上的erase 与关联容器上的erase 的工作方式不同。

标签: c++ g++ std composite-component


【解决方案1】:

i 可能不再有效(因为您已删除它)时,您不能循环访问您的容器并说++i。一个典型的擦除循环是这样的:

for (it = x.begin(); it != x.end(); /* nothing here! */)
{
  if (must_erase(*it))
  {
    x.erase(it++); // advance it while still valid, return previous and erase
  }
  else
  {
    ++it;
  }
}

本着这种精神重写你的代码。

说明您的问题:在Object::update() 中,您调用HealthComponent::update(),这会使迭代器i 无效,然后您调用++i,这是未定义的行为。

【讨论】:

  • @Ken:erase 的所有版本都将迭代器返回到下一个元素吗?很确定set 不会这样做,因此我的语法不够优雅。 (虽然我猜 OP 使用 member-find 暗示它是 std::list。)
  • 对不起,你是对的。序列支持这种语法(并且实际上需要它),但关联容器不支持它。关联容器支持find,但序列容器不支持。对于那些,预计将使用免费功能std::find
  • @Ken:不用担心。我也对find 感到困惑,我把它和sort 混淆了(list 有一个特殊的成员)。如所写,OP 似乎正在使用关联容器。很高兴我们解决了这个问题:-)
  • 我其实很喜欢这个主意。我将发送一个状态,它被删除或类似的东西。
  • std::find 是线性复杂度,因为它遍历所有元素。但是集合和映射是排序平衡的二叉树,所以搜索复杂度是对数的。这就是为什么它有自己的查找功能。我实际上不记得我在哪里看到这个东西,但我已经读过关联树永远不要使用 std::find 因为它有更好的功能。
【解决方案2】:

在 MSVC 中擦除将返回下一个有效迭代器,但在 GCC 中它返回 void,因此处理此问题的唯一可移植方法是保留前一个迭代器,擦除当前元素,然后为下一次迭代递增前一个迭代器。

http://www.cplusplus.com/reference/stl/map/erase/

  void Object::removeComponent(string component, ComponentMap::iterator& _prev ) 
  {
     ComponentMap::iterator i = components.find(component);
     if(i == components.end()) 
        return;
     _prev = i;
     --_prev;
     components.erase(i);
     ++prev;
   }

【讨论】:

  • 你好。有趣的是我正在使用 GCC 并且我得到了正确的输出。 :/ 我会看看我能做什么。谢谢。
猜你喜欢
  • 2021-05-08
  • 2011-04-14
  • 1970-01-01
  • 2011-11-13
  • 2018-01-27
  • 2017-08-06
  • 2013-04-22
  • 2011-07-13
相关资源
最近更新 更多