【问题标题】:Program crashes when attempting to erase the last std::vector element尝试擦除最后一个 std::vector 元素时程序崩溃
【发布时间】:2017-12-19 22:43:57
【问题描述】:

我正在遍历矢量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) 调用使程序崩溃。 当崩溃发生时,我收到一个返回码:134 (0x86)。这段代码有什么问题?

(*it)-&gt;getSprite()Bullet 类返回一个指向精灵的指针。

【问题讨论】:

  • std::vector::erase 使迭代器无效,使++it 在调用erase 之后,出现未定义的行为。
  • 为什么不std::vector::pop_back()
  • 我试图做这样的事情: if (std::next(it) == bullets.end()) bullets.pop_back();否则子弹.擦除(它);但它似乎也不起作用。
  • 无论it 是否引用最后一个元素,这段代码都表现出未定义的行为。 bullets.erase(it); ++it; 序列无论哪种方式都无效。如果它看起来有效,那只是偶然。您可以通过以下方式挽救它:for (iterator it = ...; ...; /* no increment */) { if (condition) { it = bullets.erase(it); } else { ++it; } }
  • 哦,好的,所以要清楚一点,在我之前的代码中,当我删除元素时,迭代器已经指向下一个元素,并且在下一次迭代的“for”循环中我调用了 + +它所以它指向另一个元素?在最后一个子弹的情况下,在 ++it 调用之后,它指向 bullets.end() 并导致错误?

标签: c++ memory vector crash sfml


【解决方案1】:

如何使用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());

【讨论】:

    【解决方案2】:

    供您参考:

    以下代码 sn-p 显示了我如何从尾部清除向量(使用 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.
    

    【讨论】:

    • 注意计算 Hit.size() 与 Missed.size() 的机会。
    【解决方案3】:

    并发现 bullet.erase(it) 调用 [针对最后一个元素] 程序崩溃

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

    考虑以下几点:

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

    您的代码找到每个元素,并使用 bullets.erase() 在向量中创建 100 个“洞”。因为矢量数据保持连续,擦除方法也会移动其他元素来填充擦除创建的空洞。 (实现之间的差异细节。)

    通常,100 次擦除会导致 100 次随机播放,每次少于(最多)1000 个元素...这种一次一个的方法可能是一个相对“缓慢”的过程。


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

    • 您可以用同样的方式找到相交(命中),但要“标记”它们,不要擦除它们。选项包括向 Bullet 类添加一个布尔值,或维护一个匹配的布尔向量来为每个项目符号保存此标志。

    • 使用两个索引,

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

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

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

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

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

    重复直到 i1 >= i2

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

    这应该消除任何洗牌。

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

    【讨论】:

    • 缺点当然是剩下的'错过'的子弹不再按原来的顺序排列了。
    猜你喜欢
    • 2016-03-31
    • 1970-01-01
    • 2021-07-12
    • 1970-01-01
    • 1970-01-01
    • 2015-04-20
    • 1970-01-01
    • 1970-01-01
    • 2012-02-18
    相关资源
    最近更新 更多