unique_ptr 删除向量?

vector of unique_ptr deleting?

我有一个我无法弄清楚的段错误问题。它来自我正在开发的小型游戏引擎的 EntityManager。 我可以添加 Ship Entity,Ship 可以添加 1 Bullet Entity,但如果我尝试添加超过 1 Bullet,它会出现段错误。在过去的一天里,我一直在努力解决这个问题。以下是实际代码的一小段摘录。

#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;
}

在实际代码中,所有内容都在自己的.h/.cpp 文件中,类 而不是结构,但问题是一样的。 输出是`Adding Bullet // Bullet ctor // Completed Loop #1 // Adding Bullet // Bullet ctor // Signal: SIGSEGV (Segmentation fault)

段错误发生在 entity->update(); 行的 EntityManager::update() 中。

问题是这个循环修改了向量:

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

当您修改向量以添加新元素时,您正忙于遍历它,这会使用于遍历容器的迭代器失效。

基于范围的 for 循环被编译器扩展为:

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

begin->update() 的调用会向向量添加一个新元素,这会使容器中的所有迭代器失效,因此 ++begin 是未定义的行为。实际上,begin 不再指向向量(因为它已经重新分配并释放了 begin 指向的旧内存)所以下一个 begin->update() 调用取消引用一个无效的迭代器,访问释放内存和段错误。

为了安全地做到这一点,您可能希望使用索引而不是迭代器:

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

这会捕获循环开始时的大小,因此只会迭代到循环开始时存在的最后一个元素,因此不会访问添加到末尾的新元素。

这在修改矢量时仍然有效,因为您不存储迭代器或指向元素的指针,只存储索引。只要您不从向量中删除元素,即使在插入新元素后索引仍然有效。