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();
这会捕获循环开始时的大小,因此只会迭代到循环开始时存在的最后一个元素,因此不会访问添加到末尾的新元素。
这在修改矢量时仍然有效,因为您不存储迭代器或指向元素的指针,只存储索引。只要您不从向量中删除元素,即使在插入新元素后索引仍然有效。
我有一个我无法弄清楚的段错误问题。它来自我正在开发的小型游戏引擎的 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();
这会捕获循环开始时的大小,因此只会迭代到循环开始时存在的最后一个元素,因此不会访问添加到末尾的新元素。
这在修改矢量时仍然有效,因为您不存储迭代器或指向元素的指针,只存储索引。只要您不从向量中删除元素,即使在插入新元素后索引仍然有效。