【发布时间】:2013-12-08 11:16:04
【问题描述】:
我创建了一个MemoryManager<T> 类,它基本上是两个指针向量的包装器,用于管理堆分配对象的生命周期。
一个向量存储“活动”对象,另一个存储将在下一个MemoryManager<T>::refresh 添加的对象。
选择这种设计是为了避免在循环MemoryManager<T> 时使迭代器失效,因为直接向MemoryManager<T>::alive 向量添加新对象会使现有迭代器失效(如果它的大小增加)。
template<typename T> struct MemoryManager {
std::vector<std::unique_ptr<T>> alive;
std::vector<T*> toAdd;
T& create() {
auto r(new T);
toAdd.push_back(r);
return *r;
}
T& refresh() {
// Use erase-remove idiom on dead objects
eraseRemoveIf(alive, [](const std::unique_ptr<T>& p){ return p->alive; });
// Add all "toAdd" objects and clear the "toAdd" vector
for(auto i : toAdd) alive.emplace_back(i);
toAdd.clear();
}
void kill(T& mItem) { mItem.alive = false; }
IteratorType begin() { return alive.begin(); }
IteratorType end() { return alive.end(); }
}
我在我的游戏引擎中使用它来存储实体,并在每一帧更新每个“活着”的实体:
void game() {
MemoryManager<Entity> mm;
while(gameLoop) {
mm.refresh();
for(auto e : mm) processEntity(e);
auto& newEntity = mm.create();
// do something with newEntity
}
}
这让我可以不断地创建/杀死实体,而不必过多担心它们的生命周期。
但是,我最近得出的结论是,没有必要使用两个 std::vector。我可以简单地使用单个向量并将迭代器存储到“最后一个存活对象”,在上述迭代器之后立即添加新创建的对象:
在我看来,这个想法很好用……但我实际上不能为 end 使用迭代器类型(如图所示),因为在向向量添加一些新元素后它可能会失效.我已经测试过了,这种情况经常发生,导致崩溃。
我能想到的另一个解决方案是使用索引而不是迭代器。这将解决崩溃问题,但我无法使用酷炫的 C++11 for(x : y) foreach 循环,因为 MemoryManager<T>::begin 和 MemoryManager<T>::end 需要返回一个迭代器。
有没有办法用单个向量实现当前行为,并且仍然保持清晰的接口,可以与 C++11 for-each 循环一起使用?
【问题讨论】:
-
无关:为什么
toAdd不是智能指针向量? -
@Casey:哎呀,你是对的。它应该是
std::unique_ptr的向量,因为销毁MemoryManager<T>不会手动释放toAdd的项目分配的内存。 -
为什么不直接写
IteratorType end() { return alive.begin() + aliveCount; } -
@zch:因为在单次迭代期间,
alive.begin()迭代器可能会在更新期间添加新实体时失效。 -
@DyP:谢谢,修正了那个错字。
标签: c++ c++11 vector iterator invalidation