假设有这样一个管理对象的窗口 ActorManager,其实现大概为
class Actor;
class ActorManager
{
public:
void update()
{
for (actors_t::const_iterator itr = m_actors.begin(); itr != m_actors.end(); ++itr)
{
Actir* actor = itr->second;
actor->update();
}
}
void add(Actor* actor)
{
m_actors[actor->get_id()] = actor;
}
void remove(Actor* actor)
{
m_actors.erase(actor->get_id());
}
private:
typedef std::map<int, Actor*> actors_t;
actors_t m_actors;
};
而Actor类的实现是这样:
class Actor
{
public:
void update()
{
// ...
}
有一天,在给Actor添加逻辑的时候,update函数变成了这样
void update()
{
// ...
update_buff_effect();
// ...
}
再往下
class Actor
{
// ...
private:
void update_buff_effect()
{
// ...
apply_hp(-100);
if (get_hp() <= 0)
{
die();
return;
}
// ...
}
然后……
private:
void die()
{
// ...
ActorManager::getInstance().remove(this);
// ...
}
在写下ActorManager的时候并没有想到会在update循环里删除对象,而实际上却有几次遇到类似的问题。
有些问题没有这么明显,但也都是出在遍历容器对象的过程中,某个执行函数删除了窗口里的对象,从而导致迭代器失效。
修改的方法很简单,给ActorManager添加一个待删除对象列表
在remove方法中并不真正删除对象,而是等到update中循环结束后再删除对象。
代码看起来会是这样:
class Actor;
class ActorManager
{
public:
void update()
{
m_is_looping = true;
for (actors_t::const_iterator itr = m_actors.begin(); itr != m_actors.end(); ++itr)
{
Actir* actor = itr->second;
actor->update();
}
m_is_looping = false;
if (!m_removed_actors.empty())
{
for (removed_actors_t::const_iterator itr = m_removed_actors.begin();
itr != m_removed_actors.end(); ++itr)
{
Actor* actor = *itr;
m_actors.erase(actor->get_id());
}
m_removed_actors.clear();
}
}
void add(Actor* actor)
{
m_actors[actor->get_id()] = actor;
}
void remove(Actor* actor)
{
if (!m_is_looping)
m_actors.erase(actor->get_id());
else
m_removed_actors.push_back(actor);
}
private:
typedef std::map<int, Actor*> actors_t;
actors_t m_actors;
typedef std::vector<Actor*> removed_actors_t;
removed_actors_t m_removed_actors;
bool m_is_looping;
};
没有给add也加保护的原因是,不会在update函数内向ActorManager添加新对象。
当然,有可能在其他地方会有这样的需求,同样也做类似的保护即可。
问题虽然不大,但是几次碰到类似的错误了。记录之,并强制要求自己,
在遇到会对容器内的对象做for…处理时,一定要谨慎的检查一下remove接口。