List iterator not incrementable断言问题:碰撞检测迭代删元素致异常
分析与解决「List iterator not incrementable」断言错误
问题现象
运行时触发
List iterator not incrementable断言错误。
背景代码
DoCollisions是每帧遍历ColliderList检测碰撞的回调函数:
void Collision::DoCollisions(Game *game){ for (ColliderList::const_iterator colliderAIt = colliders_.begin(); colliderAIt != colliders_.end(); colliderAIt++) { ColliderList::const_iterator colliderBIt = colliderAIt; for (++colliderBIt; colliderBIt != colliders_.end(); ++colliderBIt) { Collider *colliderA = *colliderAIt; Collider *colliderB = *colliderBIt; if (CollisionTest(colliderA, colliderB)) { game->DoCollision(colliderA->entity, colliderB->entity); } } } }
当碰撞检测通过时,游戏实体的析构函数会调用DestroyCollider从colliders_列表中移除对应碰撞器:
void Collision::DestroyCollider(Collider *collider){ colliders_.remove_if(std::bind1st((std::equal_to<Collider *>()), collider)); delete collider; }
错误原因
这个断言错误的核心是迭代器失效:
- 在
DoCollisions的嵌套循环中,你正通过colliderAIt和colliderBIt遍历colliders_列表。 - 当
CollisionTest返回true时,game->DoCollision会触发实体析构,进而调用DestroyCollider使用remove_if从colliders_中删除目标碰撞器。 - 对于
std::list来说,一旦某个元素被移除,指向该元素的迭代器就会立即失效。如果此时循环尝试对失效的迭代器执行++操作(比如colliderAIt++或colliderBIt++),就会触发List iterator not incrementable的断言错误。
举个具体场景:假设当前colliderAIt指向的碰撞器在碰撞处理中被销毁,那么下一次循环执行colliderAIt++时,这个迭代器已经失效,直接触发断言。
解决方案
这里提供两种常用的可靠解决思路:
方案1:延迟移除碰撞器
不要在碰撞遍历过程中直接修改colliders_列表,而是先记录需要销毁的碰撞器,等遍历完成后再统一处理:
- 在
Collision类中添加一个临时容器,用于存储待销毁的碰撞器:std::vector<Collider*> collidersToDestroy_; - 修改
DestroyCollider,把要销毁的碰撞器加入临时列表,而非立即移除:void Collision::DestroyCollider(Collider *collider){ collidersToDestroy_.push_back(collider); } - 在
DoCollisions遍历完成后,统一处理待销毁的碰撞器:void Collision::DoCollisions(Game *game){ // 原有的碰撞检测遍历逻辑... // 遍历结束后处理销毁 for (Collider* collider : collidersToDestroy_) { colliders_.remove_if([collider](Collider* c) { return c == collider; }); delete collider; } collidersToDestroy_.clear(); }
这种方法彻底避免了遍历过程中修改列表导致的迭代器失效问题,是最安全的做法之一。
方案2:使用安全的迭代器遍历方式
如果你不想延迟销毁,可以在遍历前提前保存下一个迭代器,这样即使当前迭代器指向的元素被移除,我们依然可以通过保存的下一个迭代器继续循环:
void Collision::DoCollisions(Game *game){ for (auto colliderAIt = colliders_.begin(); colliderAIt != colliders_.end(); ) { // 提前保存下一个迭代器 auto nextColliderA = std::next(colliderAIt); auto colliderBIt = std::next(colliderAIt); for (; colliderBIt != colliders_.end(); ) { // 提前保存B的下一个迭代器 auto nextColliderB = std::next(colliderBIt); Collider *colliderA = *colliderAIt; Collider *colliderB = *colliderBIt; if (CollisionTest(colliderA, colliderB)) { game->DoCollision(colliderA->entity, colliderB->entity); } // 使用保存的下一个迭代器推进循环 colliderBIt = nextColliderB; } // 使用保存的下一个迭代器推进循环 colliderAIt = nextColliderA; } }
同时,建议把DestroyCollider中的旧风格bind1st替换为C++11+的Lambda表达式,可读性更好:
void Collision::DestroyCollider(Collider *collider){ colliders_.remove_if([collider](Collider* c) { return c == collider; }); delete collider; }
内容的提问来源于stack exchange,提问作者Josh47
相关产品推荐
相关产品推荐

