我正在为学校制作的游戏编写EventManager类。它使用事件的双队列系统,并保存一个由事件类型和已注册ID向量组成的无序映射,以响应特定事件。我遇到的问题是,某些事件(如单击主菜单的播放按钮)可能会导致参与者取消注册事件。虽然这是有意的,但它会导致迭代器出现问题,而这并不是有意的。在从主菜单切换到游戏的示例中,它会破坏主菜单中的所有参与者(按钮等)。反过来,它们会用事件管理器注销自己,事件管理器会从存储在unrdered_map中的相应向量中删除它们的id,并使在循环开始时抛出异常的迭代器无效。因此,任何事件都不会导致参与者注销任何内容。这是不可取的,因为游戏中的一些触发对象可能是一次性触发,在这一点上,它们需要注销自己以接收更多事件。如有任何帮助或想法,我们将不胜感激。
void EventManager::Flush()
{
Event* current;
while (m_eventList[m_current].size() > 0)
{
current = m_eventList[m_current].front();
m_eventList[m_current].pop_front();
unordered_map<string, vector<IDTYPE>>::iterator it = m_registeredEvents.find(current->GetType());
if (it == m_registeredEvents.end())
continue;
vector<IDTYPE>* toProcessAct = &(it->second);
vector<IDTYPE>::iterator actIt = toProcessAct->begin();
while (actIt != toProcessAct->end()) //this becomes invalid
{
Actor* temp = ACTORS->GetActor(*actIt);
if (temp == NULL)
actIt = toProcessAct->erase(actIt);
else
{
actIt++;
temp->Process(current); //Because this may unregister events
}
}
delete current; current = 0;
}
Swap();
}
void EventManager::UnregisterEvent(string Event, IDTYPE actor)
{
unordered_map<string, vector<IDTYPE>>::iterator it = m_registeredEvents.find(Event);
//Found
if (it != m_registeredEvents.end())
{
//Find if actor is already registered
vector<IDTYPE>::iterator actIt = it->second.begin();
while (actIt != it->second.end())
{
if (actor == *actIt)
{
it->second.erase(actIt);
return;
}
actIt++;
}
}
}
这是射中自己脚部的好方法
Event* current; // not the issue here but you should always initialize your variables.
while (m_eventList[m_current].size() > 0) {
current = m_eventList[m_current].front(); // current points the the first element
m_eventList[m_current].pop_front(); // now the first element gets destroyed
unordered_map<string, vector<IDTYPE>>::iterator it = m_registeredEvents.find(current->GetType()); // its pure coincident if this works.
pop_front()破坏front元素,使指针无效,然后输入未定义的行为。制作一份记录的副本,这样你也不会有销毁它的问题。
此外,删除后元素的所有迭代器都是无效的,所以如果您的代码将迭代器保存为一种状态,您会受到伤害,那么为了安全起见,假设所有删除都会使该容器或相关容器的所有迭代器无效。
如果你正在运行一个多线程程序,那么其他线程可能会更改内容,那么至少要使用一个互斥锁来保护它
好的,现在是你真正想要的:
while (actIt != toProcessAct->end()) { // end() should update but actIt doesn't.
Actor* temp = ACTORS->GetActor(*actIt);
if (temp == NULL)
actIt = toProcessAct->erase(actIt);
else {
actIt = temp->Process(actIt, current); // make Process return the new valid It
}
}
delete current; // would fail as there is a continue higher up!!!
current = 0; // if using C++11 use nullptr instead else NULL
如果在actIt之后没有有效的迭代器,则让Process返回新的有效迭代器或toProcessAct->end()。
一种解决方案可以是迭代事件处理程序向量的副本,而不是
vector<IDTYPE>* toProcessAct = &(it->second);
写入
vector<IDTYPE> toProcessAct = it->second;
(并对要编译的代码进行相应的更改)
此外,如果在Flush()
开始时复制整个m_registeredEvents
,可能会更快,因此不必多次复制向量。
另一种解决方案是推迟注销,即UnregisterEvent()
将注销请求排入单独的队列,然后再执行,例如在Flush()
结束时,而不是立即执行。