Vector unique_ptr deletion?

I have a problem with segfault that I cannot understand. This is from the EntityManager for the small game engine I'm working on. I can add Ship Entity , and the Ship can add 1 Bullet Entity , but it segfaults if I try to add more than 1 Bullet . I tried to figure it out the last day. The following is a small excerpt from the actual code.

 #include <vector> #include <memory> struct EntityManager; struct Entity { Entity(EntityManager* manager) : manager(manager) { } virtual ~Entity() { } virtual void update() = 0; EntityManager* manager; }; struct EntityManager { void update() { for (auto& entity : entities) { entity->update(); } } void add(Entity* e) { entities.emplace_back(e); } std::vector<std::unique_ptr<Entity>> entities; }; struct Bullet : public Entity { Bullet(EntityManager* manager) : Entity(manager) { printf("Bullet ctor\n"); } virtual void update() override { } }; struct Ship : public Entity { Ship(EntityManager* manager) : Entity(manager) { } virtual void update() override { printf("Adding Bullet\n"); manager->add(new Bullet(manager)); } }; int main() { EntityManager manager; manager.add(new Ship(&manager)); int loops{0}; while (loops < 100) { manager.update(); loops++; printf("Completed Loop #%d\n", loops); } return 0; } 

In the actual code, everything is in its .h / .cpp files and classes instead of structures, but the problem is the same. The result is `Add Bullet // Bullet ctor // Finished Loop # 1 // Add Bullet // Bullet ctor // Signal: SIGSEGV (segmentation error)

The segfact occurs in EntityManager::update() in the line entity->update(); .

+6
source share
1 answer

The problem is that this loop modifies the vector:

  for (auto& entity : entities) { entity->update(); } 

You iterate through it when you modify the vector to add a new element, which invalidates the iterators used to move the container.

The range-based for loop is extended by the compiler to:

 auto begin = entities.begin(), end = entities.end(); for (; begin != end; ++begin) begin->update(); 

The call begin->update() adds a new element to the vector, which invalidates all iterators in the container, so the ++begin behavior is ++begin undefined. From a practical point of view, begin no longer points to the vector (since it redistributes and frees the old memory that begin points to), so the next call to begin->update() calls an invalid iterator, accessing the freed memory and seg-faulting.

To do this safely, you probably want to use indexes rather than iterators:

 for (size_t i = 0, size = entities.size(); i != size; ++i) entities[i].update(); 

This fixes the size at the beginning of the cycle and therefore only iterates to the last element that exists when the cycle starts, so new elements added to the end will not be visited.

This still works when the vector changes because you do not store iterators or pointers to elements, but only an index. Until you remove elements from the vector, the index remains valid even after inserting new elements.

+13
source

All Articles