C++中遍歷容器對象時需要注意的問題
假設有這樣一個管理對象的窗口 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接口。
posted on 2010-08-12 23:02 白云哥 閱讀(2982) 評論(15) 編輯 收藏 引用 所屬分類: Others