尝试擦除最后一个 std::vector 元素时程序崩溃

Program crashes when attempting to erase the last std::vector element

我正在遍历向量 std::vector<Bullet*> bullets,我正在寻找与敌人的碰撞。它在任何情况下都很好用,除了以下情况:最后发射的子弹(必须不止一颗)与敌人相撞。

代码-

for(std::vector<Bullet*>::iterator it = bullets.begin(); it != bullets.end(); ++it)
{
    if ((*it)->getSprite()->getGlobalBounds().intersects(enemy->getSprite()->getGlobalBounds()))
    {
        delete *it;
        bullets.erase(it);
        enemy->destroy();
        if (bullets.size() == 0)
            break;
    }
}

我评论了 for 循环中的特定元素,发现 bullet.erase(it) 调用使程序崩溃。 当崩溃发生时,我收到一个 return 代码:134 (0x86)。该代码有什么问题?

(*it)->getSprite() returns 指向来自 Bullet class.

的精灵的指针

如何使用 remove_iferase 组合:

auto is_hit = [&enemy](Bullet *bullet)
{
    if (bullet->getSprite()->getGlobalBounds().intersects(enemy->getSprite()->getGlobalBounds()))
    {
        delete bullet;
        enemy->destroy();
        return true;
    }
    return false;
};

bullets.erase(std::remove_if(bullets.begin(), bullets.end(), is_hit), bullets.end());

供您考虑:

下面的代码片段展示了我如何从尾部清除矢量(使用 push_back() 向尾部添加元素的补充操作)

while(!gBoard.empty())
{
   Cell_t* cell = gBoard.back();  // fetch last element (a ptr)
   gBoard.pop_back();             // remove last element
   delete cell;                   // remove cell from heap - raw pointer
}  

也许您可以采用这种清理方式并使用多个向量...它仍然可能比其他方法更快。

在您的问题中,每颗子弹似乎至少有两个目的地……命中或未命中。

while ( ! Bullets.empty() )   // spin through bullet list
{
    Bullet* aBullet = Bullets.back();  // fetch copy of last element
    Bullets.pop_back();                // remove last element 

    if (*aBullet)-> getSprite()->getGlobalBounds().    
           intersects(enemy->getSprite()->getGlobalBounds()))
    {  
       // HIT!
       Hit.push_back(aBullet); // capture the element to Hit bucket
       enemy->destroy();       // tbd - a decision? or always final?
       // no delete 
       if (bullets.size() == 0) // no more to compute, redundant to while
           break;
    }
    else
    {
       // MISS 
       Missed.push_back(aBullet);  // capture element to Missed bucket
    }
} // while 

assert(bullets.empty());  // bullets have been consumed

// clean up spent bullets that intersected
while (! Hit.empty() )
{
   Bullet* aBullet = Hit.back(); // copy last element from Hit
   Hit.pop_back();               // remove last element from Hit
   delete aBullet;               // tbr - delete the dynamic memory
}

// clean up spent bullets that missed 
// move the bullet from Missed vec back into Bullets vec
//    for tbd - furthur evaluation ... did the bullet hit any other obj
// the following also happens to 'undo' the seq reversal
while (! Missed.empty() )
{
   Bullets.push_back (Missed.back()); // copy last element from Missed
   Missed.pop_back();                 // remove last element from Missed
   // tbd - also delete the missed bullet?
   //   or do you check for these bullets to collide with other objects
}
// possibly a copy can do this last loop, but this is simple and 
// undoes the reversal.

and found out that the bullet.erase(it) call [for that last element] crashes the program

从某种意义上说,您可能过早地执行了擦除。

考虑以下几点:

射程、目标和武器类型的测试参数可能会结合起来实现,例如,10% 的命中率。因此,在 1000 次射击的集合中,(1000 == bullets.size()),将有 (~) 100 颗子弹击中目标。

您的代码找到每个元素,并使用 bullets.erase() 在向量中创建 100 'holes'。因为矢量数据保持连续,所以 erase 方法还会移动其他元素来填充由 erase 创建的空洞。 (有关实现之间可能有何不同的详细信息。)

一般来说,100 次擦除会导致 100 次每次少于(最多)1000 个元素的洗牌……这种一次一个的方法可能是一个相对 'slow' 的过程。


作为当前设计的替代方案,而不是查找和擦除,推迟擦除直到您的代码识别并标记所有 'intersects'。

  • 你可以用同样的方法找到相交(命中),但是'mark'它们,先不要擦掉它们。选项包括向 Bullet class 添加一个 bool,或者维护一个匹配的 bool 向量来为每个 bullet 保存此标志。

  • 使用两个索引,

    -- i1 初始化为 0(第一个(最左边)向量元素)和

    -- i2 初始化为 (bullets.size() - 1) [最后(最右边)向量元素]

-- 自旋增量 i1 找到第一个命中,

-- 自旋递减 i2 找到最后一个未命中,

-- 然后 std::swap (bullets[i1], bullets[i2])

重复直到 i1 >= i2

现在所有命中都是连续的并且位于向量的尾部,对 100 个命中执行一次擦除

这应该可以消除任何洗牌。

此外,不应使用已擦除的元素...因为擦除发生在过程结束时。