我有一些课程:
struct Listenable{
virtual void removeListener(Listener * listener) = 0;
};
class Listener{
public: //that way example is simpler
unsigned myCode = 0;
Listenable * subject = 0;
Listener(unsigned myCode, Listenable * subject)
: myCode(myCode), subject(subject){}
void notify(unsigned value){
if(value == myCode){
a->removeListener(this);
}
}
};
class A : public Listenable{
public: //that way example is simpler
std::vector<Listener*> listeners;
void fun(unsigned value){
for(auto listener : listeners){
b->notify(value);
}
}
void removeListener(Listener * listener){
auto it = std::find(listeners.begin(), listeners.end(), listener);
if(it != listeners.end()){
listeners.erase(it);
}
}
};
和代码:
A a;
Listener * l1 = new Listener(5, a);
Listener * l2 = new Listener(7, a);
a.listeners.push_back(l1);
a.listeners.push_back(l2);
a.notify(3); //OK
a.notify(5); //error
我在vector iterator not incrementable
中收到a.notify(5)
错误。
我知道这是因为当我通知l1
听众(在for
的{{1}}循环内)时,它决定取消订阅(致电{{1} })。
但是如何解决这个问题?我想迭代抛出所有侦听器并通知他们一个事件。 我不能假设,如果其中任何一个(或其中有多少个)想要从列表中删除它(它可能是对事件或其他地方的反应)。我也不能假设哪种情况会强制特定听众调用A::fun(5)
和时。
我可以将A::removeListener
更改为A::removeListener(this)
void notify(...)
意味着&#34;请取消我&#34;。 但我无法确定该用户无论如何都不会在自定义bool notify(...)
(来自继承自return true
的类)中调用A::removeListener(this)
。< / p>
答案 0 :(得分:3)
如果在迭代时向量可能会被更改,那么只有一种方法可以去:
迭代副本!
当然,除非你可以改变数据结构。
答案 1 :(得分:2)
这似乎是std::list
的可能用例,其中迭代器在列表更改时不会失效(除非它们引用的东西被删除)。
如果您有std::list<Listener*>
,则可以使用两个迭代器current
和next
(例如)逐步查看列表,将next
保持在{{1}之前在每次迭代中都要通知current
,并确保*current
之后仍然是一个有效的迭代器。然后设置next
,问题就会巧妙地回避。
答案 2 :(得分:1)
如果您无法控制您的侦听器是否可能尝试从thew集合中删除自己,则应考虑使用具有稳定迭代器的容器。一个很好的例子是来自Boost的stable_vector类(参见这里的文档:http://www.boost.org/doc/libs/release/doc/html/container/non_standard_containers.html#container.non_standard_containers.stable_vector)。 它的内存占用当然比std :: vector更大,但整体算法的复杂性是相同的。只要元素存在于容器中,您获得的一个好特性是特定元素迭代器保持有效。您可以像这样重写迭代,以使其容忍删除:
for (auto iter = listeners.begin(); iter != listeners.end(); ) // note we don't auto increment here
auto next_iter = iter + 1; // remember the next element
iter->do_something(); // may remove itself from the container
iter = next_iter;
}
编辑:或者,正如wintermute建议的那样,您可以使用std::list
作为稳定容器,尽管它在迭代方面的性能相当差,并且其内存使用效率低于一个任何载体。
答案 3 :(得分:1)
另一种方法是将元素标记为抑制,并在循环后删除它,如:
class A : public Listenable{
public: //that way example is simpler
std::vector<Listener*> listeners;
void fun(unsigned value){
for (auto listener : listeners){
if (listener) { // not marked as deleted
listener->notify(value);
}
}
// remove 'mark_as_deleted' listeners
listeners.erase(std::remove(listeners.begin(), listeners.end(), nullptr),
listeners.end();
}
void removeListener(Listener* listener){
auto it = std::find(listeners.begin(), listeners.end(), listener);
if (it != listeners.end()){
*it = nullptr; // mark as deleted.
}
}
};
答案 4 :(得分:1)
向班级isNotifying
添加布尔标志A
。
将此标记设置为true
开头的fun
和false
末尾的fun
。
在removeListener
内,检查标志。如果是false
,只需像现在一样删除侦听器。否则,将侦听器添加到将来要删除的第二个侦听器向量中。
在fun
的最后,从所述侦听器向量中删除所有侦听器。然后清除所说的载体。
如果fun
需要重新进入,请使用int
代替布尔标记并向上/向下计数。