Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

vector of unique_ptr deleting?

I have an issue with a segfault that I can not figure out. It's from an EntityManager for a small game engine I'm working on. I can add the Ship Entity, and the Ship can add 1 Bullet Entity, but it segfaults if I try to add more than 1 Bullet. I've been trying to figgure this out for the past day now. Below 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 their own .h/.cpp files, and classes instead of structs, but the issue is the same. The output is `Adding Bullet // Bullet ctor // Completed Loop #1 // Adding Bullet // Bullet ctor // Signal: SIGSEGV (Segmentation fault)

The segfault happens in the EntityManager::update() on the entity->update(); line.

like image 566
BFritz Avatar asked Sep 25 '15 09:09

BFritz


People also ask

Can you delete a unique_ptr?

An explicit delete for a unique_ptr would be reset() . But do remember that unique_ptr are there so that you don't have to manage directly the memory they hold. That is, you should know that a unique_ptr will safely delete its underlying raw pointer once it goes out of scope.

What happens when unique_ptr goes out of scope?

An​ unique_ptr has exclusive ownership of the object it points to and ​will destroy the object when the pointer goes out of scope.

What happens to unique_ptr after move?

"Moving" transfers ownership to a new unique_ptr and resets the old unique_ptr .


1 Answers

The problem is that this loop modifies the vector:

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

You are busy iterating through it when you modify the vector to add a new element, which invalidates the iterators being used to traverse the container.

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

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

The call to begin->update() adds a new element to the vector, which invalidates all iterators into the container, so the ++begin is undefined behaviour. In practical terms, begin no longer points into the vector (because it has reallocated and freed the old memory that begin pointed to) so the next begin->update() call dereferences an invalid iterator, accessing freed memory and seg-faulting.

To do it safely you probably want to use indices not iterators:

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

This captures the size at the start of the loop and so only iterates up to the last element that exists when the loop starts, so new elements added to the end will not get visited.

This still works when the vector is modified because you don't store iterators or pointers to elements, only an index. As long as you don't remove elements from the vector the index is still valid even after inserting new elements.

like image 131
Jonathan Wakely Avatar answered Sep 20 '22 10:09

Jonathan Wakely