擦除(和删除)时需要手动控制迭代器

Manual control of iterator when erasing (and deleting) is necessary

本文关键字:控制 迭代器 删除 擦除      更新时间:2023-10-16

在粒子系统中,一旦粒子足够老,它们就需要死亡。由于它们存储在std::vector中,因此在XCode中运行良好的方法是:

for(std::vector<Particle*>::reverse_iterator iter = particles.rbegin(); iter != particles.rend(); ++iter) {  
    (*iter)->update();  
    if ( (*iter)->isDead() ) {
        delete (*iter);
        particles.erase( --iter.base() );  
    }  
}

在启动到Windows并在VisualStudio2010中编译后,我发现它不起作用:请参阅此处。正如答案本身所说,这对关联容器不起作用。我发现这里最令人沮丧的是std::reverse_iteratorstd::iterator的行为不同:

  • .erase不接受reverse_iterator,并且想要真实的东西(例如,参见此)
  • rev_it.base()调用需要在擦除调用中递减
  • 擦除后,我需要将std::iterator转换为std::reverse_iterator

我曾想过使用前向std::iterator,但向后迭代,这是一个糟糕的想法——但向后迭代的真正需要是确保循环不会跳过已擦除particles的相邻成员。

不过,对我来说有意义的是,如果进行了.erase()调用,就不迭代

for( std::vector<Particle*>::iterator iter = particles.begin(); iter != particles.end(); ) {  
    (*iter)->update();  
    if ( (*iter)->isDead() ) {  
        delete (*iter);
        iter = particles.erase(iter);  
    } else {
        ++iter;
    }
}

这可以编译、工作,而且似乎不是问题。然而,问题是:

我是不是忽略了一些让这个想法变得特别愚蠢的东西

(我确信iter会利用.erase()函数的return值指向正确的下一个值,而且对我来说,它似乎比--iter.base()调用更可读。)

撇开这句话不谈,我脑海中浮现的一句俄语谚语是"被热牛奶烧伤的人会被冷水烫伤。"

除了其他答案(尤其是juancopanza的答案),您还可以使用单个std::remove_if:

particles.erase(std::remove_if(particles.begin(), particles.end(), 
                               [](Particle *particle) -> bool {
                                   bool dead = p->isDead();
                                   if(dead)
                                       delete p;
                                   return dead;
                               }),
                particles.end());

(如果没有C++11 lambdas,请随意使用自定义函子。)

这将起作用,因为元素的可能重复将发生在为其评估谓词之后,并且它已经被删除,因此谓词仍然只对向量中的每个元素调用一次,而不会对任何可能的重复调用。新的结束迭代器之后的值包含什么是完全无关的,因为我们在之后erase它们,而std::vector::erase根本不尝试delete


编辑:当然,另一种选择是为粒子使用智能指针(特别是C++11的std::unique_ptr s,或者,如果您对主题有很好的理解并完全理解您正在做的事情,则为std::shared_ptr s)。这至少可以让你不需要手动管理他们的内存。在这种情况下,您可以直接将isDead方法映射到谓词函数,而根本不需要lambda(并且您不需要修改谓词内部的范围,这仍然有点单一):

std::vector<std::unique_ptr<Particle>> particles;
...
particles.erase(std::remove_if(particles.begin(), particles.end(), 
                               std::mem_fn(&Particle::isDead)),
                particles.end());

编辑:尽管我们在做这件事的时候,我还是忍不住问你一个问题,这些粒子是否需要动态分配,std::vector<Particle>最终是否可能不能同样好地工作(但很可能你有充分的理由在这里使用指针)。

您的第二段代码很好。当我需要从正在迭代的列表中删除元素时,我也会这样做。

AFAIK当需要时,"手动"控制(即手动递增迭代器)没有错。就你而言,这似乎是必要的。

我相信iter会通过取.ecrease()函数的返回值的优势,而且它似乎更比--iter.base()调用更可读。

我完全同意。

编辑:正如@n.m在评论中所说,std::remove_if在您的情况下似乎已经足够了。

我会使用两遍解决方案:

1) 删除元素并设置为NULL:

void killParticle(Particle*& p)
{
  if ( p->isDead() ) {
    delete p;
    p = NULL;  
}  
std::for_each(particles.begin(), particles.end(), killParticle);

2) 使用擦除删除习惯用法删除NULL元素:

particles.erase(std::remove(particles.begin(), particles.end(), NULL), 
                particles.end());