这是我的一般设计查询。我通过维护订阅者列表实现了发布/订阅模式。当要发布的事件发生时,我遍历订阅者并依次将事件推送给每个订阅者。
我的问题发生在由于该出版物,软件深度的某个地方,所描述的组件决定取消订阅的另一个组件或事件。通过这样做,它们使我的迭代器失效并导致崩溃。
解决此问题的最佳方法是什么?我一直在考虑将整个发布循环包装到try catch块中,但这意味着一些订阅者会错过某人取消订阅的特定订阅,并且似乎有点过头了。然后我尝试将它喂回来,例如我将void发布调用转换为bool发布调用,当订阅者想要删除时返回true,这适用于该情况,但如果另一个订阅者取消订阅则不会。然后我想在某处“缓存”取消订阅请求并在循环完成时释放它们,但这似乎有点矫枉过正。然后我想将迭代器存储为类成员,这样我就可以从外部操作迭代器,但是这会变得混乱(比如你取消订阅订阅者1,迭代器指向2,容器是一个向量 - 然后是迭代器必须减少)。我想我可能更喜欢后两种解决方案中的一种,但两者似乎都不理想。
这是一个常见问题吗?有更优雅的解决方案吗?
答案 0 :(得分:1)
您可以在发布期间禁止订阅操作,也可以使用适当的数据结构来保存订阅列表,或者两者兼而有之。
假设您将订阅者保留在std::list
,那么您可以运行循环:
for(iterator_type it = subs.begin(); it != subs.end(); ) {
iterator_type next = it;
++next;
it->notifier();
it = next;
}
这样,如果删除了当前项,则next
中仍然有一个有效的迭代器。当然,在发布期间,您仍然不能允许任意删除(如果删除了next
?)。
要允许任意删除,请将项目标记为无效并将其删除列表,直到可以安全删除:
... publication loop ...
dontRemoveItems = true;
for(iterator_type it = subs.begin(); it != subs.end(); ++it) {
if(it->valid)
it->notifier();
}
std::erase(std::remove_if(...,, IsNotValid),...);
dontRemoveItems = false;
其他地方,
... removal code:
if(dontRemoveItems) item->valid = false;
else subs.erase(item);