C++ 在循环期间更改订阅的发布服务器/订阅服务器

C++ 在循环期间更改订阅的发布服务器/订阅服务器,c++,loops,publish-subscribe,C++,Loops,Publish Subscribe,这更像是我提出的一般设计问题。我通过维护订阅者列表实现了发布/订阅模式。当发生要发布的事件时,我循环遍历订阅服务器并依次将事件推送到每个订阅服务器 我的问题发生在由于该出版物,在软件深度的某个地方,另一个组件或所述组件决定取消订阅的事件。这样做会使迭代器失效并导致崩溃 解决这个问题的最好办法是什么?我一直在考虑将整个发布循环包装成一个try-catch块,但这意味着一些订阅者错过了某个人取消订阅的特定订阅,而且似乎有点过头了。然后我尝试反馈,例如,我将void publish调用转换为bool

这更像是我提出的一般设计问题。我通过维护订阅者列表实现了发布/订阅模式。当发生要发布的事件时,我循环遍历订阅服务器并依次将事件推送到每个订阅服务器

我的问题发生在由于该出版物,在软件深度的某个地方,另一个组件或所述组件决定取消订阅的事件。这样做会使迭代器失效并导致崩溃

解决这个问题的最好办法是什么?我一直在考虑将整个发布循环包装成一个try-catch块,但这意味着一些订阅者错过了某个人取消订阅的特定订阅,而且似乎有点过头了。然后我尝试反馈,例如,我将void publish调用转换为bool publish调用,当订阅者想要删除时返回true,这在这种情况下有效,但如果另一个订阅者取消订阅,则不会返回true。然后我想在某个地方“缓存”取消订阅请求,并在循环完成后释放它们,但这似乎有点过头了。然后我考虑将迭代器存储为类成员,这样我就可以从外部操作迭代器,但这会变得很混乱(假设您取消订阅订户1,迭代器指向2,容器是一个向量,那么迭代器必须递减)。我想我可能更喜欢后两种解决方案中的一种,但两者似乎都不理想


这是一个普遍的问题吗?有更优雅的解决方案吗?

您可以在发布期间禁止订阅操作,也可以使用适当的数据结构保存订阅列表,或者两者兼而有之

假设您将订阅者保留在
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);

您可以在发布期间禁止订阅操作,也可以使用适当的数据结构来保存订阅列表,或者两者兼而有之

假设您将订阅者保留在
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);

如果您的应用程序是多线程的,您需要以某种方式保护列表;如果没有,我无法想象需要取消另一个订阅者的订阅。或者我遗漏了什么?好吧,假设一个订阅者管理各种各样的应用程序,在某个事件中他订阅和取消订阅它们。。。不是他自己取消订阅,而是他调用了托管项析构函数,然后它自己取消订阅,但这与迭代器仍然指向管理器是一样的。如果你的应用程序是多线程的,你需要以某种方式保护列表;如果没有,我无法想象需要取消另一个订阅者的订阅。或者我遗漏了什么?好吧,假设一个订阅者管理各种各样的应用程序,在某个事件中他订阅和取消订阅它们。。。不是他自己取消订阅,而是他调用了托管项析构函数,然后该析构函数自行取消订阅,但这与迭代器仍然指向管理器是一样的。我也喜欢后一种方法。在C++中需要注意的是,如果一个线程正在移除一个项目,而另一个线程正在移除它的前身,则该项可能会错误地重新添加到列表中。在垃圾收集语言中,这不会是一个问题,因为该项将被识别为死项,并在下次有东西迭代列表时被重新删除。但是,在C++中,如果在删除内存后将该项重新添加到列表中,可能会出现问题。因此,我们应该把删除限制在一个线程上。注意事项适用。我也喜欢后一种方法。在C++中需要注意的是,如果一个线程正在移除一个项目,而另一个线程正在移除它的前身,则该项可能会错误地重新添加到列表中。在垃圾收集语言中,这不会是一个问题,因为该项将被识别为死项,并在下次有东西迭代列表时被重新删除。但是,在C++中,如果在删除内存后将该项重新添加到列表中,可能会出现问题。因此,我们应该把删除限制在一个线程上。警告适用。