unique_ptr的向量是什么?

BFr*_*itz 5 c++ segmentation-fault stdvector game-engine unique-ptr

我有一个我无法弄清楚的段错误问题.这是一个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;
}
Run Code Online (Sandbox Code Playgroud)

在实际的代码中,一切都在他们自己的.h/.cpp文件中,而是在类而不是结构中,但问题是相同的.输出是`Adding Bullet // Bullet ctor // Completed Loop#1 //添加Bullet // Bullet ctor //信号:SIGSEGV(分段错误)

该段错误发生在EntityManager::update()entity->update();线.

Jon*_*ely 12

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

    for (auto& entity : entities) {
        entity->update();
    }
Run Code Online (Sandbox Code Playgroud)

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

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

auto begin = entities.begin(), end = entities.end();
for (; begin != end; ++begin)
  begin->update();
Run Code Online (Sandbox Code Playgroud)

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

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

for (size_t i = 0, size = entities.size(); i != size; ++i)
  entities[i].update();
Run Code Online (Sandbox Code Playgroud)

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

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

  • 不,那不安全.它不是"固定for循环",因为`vec.size()`会改变,并且它可以循环访问新实体,因为它们被添加.它恰好适用于上面的例子,因为`Bullet`不会修改向量,但是你会在第一个更新循环中访问这两个实体.我用我的方式编写校正循环是有充分理由的.正如我所说:_"这捕获了循环开始时的大小,因此只迭代到循环开始时存在的最后一个元素,因此添加到结尾的新元素将不会被访问."_ (3认同)