SFML开发太空侵略者:容器元素超1个时碰撞检测异常崩溃
问题根因
崩溃完全由迭代器非法使用、对象生命周期管理错误导致,具体有两点:
- 你在
Bullet类的成员函数里执行bullets.erase(j)删除当前子弹对象时,会直接销毁this指针指向的当前实例。erase操作完成后,当前正在执行的hit函数属于已经被销毁的对象,后续所有对this的访问(包括外层敌人循环的下一轮迭代、当前作用域剩余代码的执行)都会访问已释放的内存,触发未定义行为。容器里只有1颗子弹时,erase后剩余逻辑刚好没有触发非法内存访问所以没崩溃,子弹数≥2时vector内存布局变化,就直接触发致命错误退出。 - 你遍历vector擦除元素的写法本身存在迭代器失效风险:vector的
erase操作会让被擦除位置及之后的所有迭代器全部失效,虽然你擦除后立刻break没有继续用失效迭代器,但额外嵌套一层循环靠ID匹配找元素的写法完全多余,既拉低效率也容易埋bug。
修复方法
核心原则是:不要在碰撞检测逻辑中途销毁当前正在执行成员函数的子弹对象,先完成所有碰撞判断、标记需要删除的元素,确认不会再访问对象成员后再执行容器擦除操作。
修复后的参考代码如下:
void Bullet::hit(vector<Enemy>& enemies,vector<Pixel>& oneShield, vector<Bullet>& bullets, int &killedEnemies, int shotsFired) { bool bulletHit = false; // 遍历敌人做碰撞检测,命中就直接删除敌人,用erase返回的有效迭代器避免失效 for (auto enemyIt = enemies.begin(); enemyIt != enemies.end(); ) { if (enemyIt->shape.getGlobalBounds().intersects(this->shape.getGlobalBounds())) { enemyIt = enemies.erase(enemyIt); killedEnemies += 1; bulletHit = true; } else { ++enemyIt; } } // 确认子弹命中后,最后再删除当前子弹,删完立刻返回,不再访问已销毁的对象成员 if (bulletHit) { for (auto bulletIt = bullets.begin(); bulletIt != bullets.end(); ) { if (bulletIt->ID == this->ID) { bullets.erase(bulletIt); break; } ++bulletIt; } return; } }
额外注意事项:
- 遍历vector擦除元素时,循环判断条件要用
it != 容器.end(),不要用<比较迭代器;擦除元素后要接erase返回的下一个有效迭代器,不要对已经失效的迭代器做递增操作。 - 只要对象被从容器中erase,指向该对象的指针、引用、迭代器就全部失效,绝对不能再通过这些失效值访问对象成员。
- 原代码中命中敌人后再从头遍历整个敌人容器找同ID对象的逻辑完全冗余,第一次遍历到碰撞对象时直接处理即可,不需要二次遍历。
内容的提问来源于stack exchange,提问作者Dawid DXR
相关产品推荐
相关产品推荐

