我的应用程序中有一个全局事件管理器。每个组件都可以监听事件并触发它们。我们来看看窗口组件。它拥有std::vector
个窗口,它会定期迭代以处理输入等等。此外,它会注册到"keydown"
事件,以便在按下转义键时删除活动窗口。
class window : public module {
window() {
// Listen to key event
listen("keydown", [=](size_t index, int code) {
if (code == 27) {
windows[index].close();
windows.erase(windows.begin() + index);
}
});
}
void update() {
for (auto i = windows.begin(); i != windows.end(); i++) {
if (i->is_open()) // On remove, crashes at this line.
// ...
}
}
};
问题在于,当从更新循环内部或另一个线程触发"keydown"
事件时,代码崩溃。我想这是因为在擦除元素后迭代器不再有效。在不知道何时发生擦除的情况下,如何安全地迭代变化的向量?
错误表示i
无法解除引用。我尝试通过try catch块包装循环体,但是没有异常可以捕获,只是来自Visual Studio的调试断言。
答案 0 :(得分:8)
如果不知道擦除何时发生,我怎样才能安全地迭代变化的矢量?
你不能。
在被另一个线程修改时,访问std::vector
(或任何其他标准容器)是未定义的行为。
您需要确保修改向量的线程阻止任何其他线程同时访问它,例如使用互斥锁。
在C ++ 14中,您可以使用std::shared_timed_mutex
来保护向量,这样只需要从向量中读取的线程就可以获取共享锁,但是想要修改向量的线程可以采用在进行更改时进行独占锁定。
class window : public module {
std::shared_timed_mutex m_mutex;
window() {
// Listen to key event
listen("keydown", [=](size_t index, int code) {
if (code == 27) {
std::unique_lock<std::shared_timed_mutex> lock(m_mutex);
windows[index].close();
windows.erase(windows.begin() + index);
}
});
}
void update() {
std::shared_lock<std::shared_timed_mutex> lock(m_mutex);
for (auto i = windows.begin(); i != windows.end(); i++) {
if (i->is_open())
// ...
}
}
};
(N.B。我将i.is_open()
更改为i->is_open()
,因为我认为这是您在代码中真正拥有的内容。)
这只会在其他线程完成更改时有所帮助,如果更新循环触发了keydown事件,它将会死锁。
另一个解决方案是推迟从矢量中删除项目并定期清理&#34; dead&#34;项目。在修改向量时,仍然需要一个互斥锁来防止并发访问,但只能通过锁定一小段代码来避免死锁。类似的东西:
class window : public module {
std::mutex m_mutex;
std::vector<size_t> m_expired;
window() {
// Listen to key event
listen("keydown", [=](size_t index, int code) {
if (code == 27) {
windows[index].close();
std::lock_guard<std::mutex> lock(m_mutex);
m_expired.push_back(index);
}
});
}
void update() {
erase_expired();
for (auto i = windows.begin(); i != windows.end(); i++) {
if (i->is_open())
// ...
}
}
void erase_expired()
{
std::lock_guard<std::mutex> lock(m_mutex);
std::sort(m_expired.begin(), m_expired.end(), std::greater<>{});
for (auto idx : m_expired)
windows.erase(windows.begin() + idx);
m_expired.clear();
}
};