2

我有一个包含“观察者”列表的对象。这些观察者会收到通知,并且他们可能会通过从对象中添加或删除自己或其他观察者来响应此更改。

我想要一种强大的,而不是不必要的缓慢的方式来支持这一点。

class Thing {
public:
    class Observer {
    public:
        virtual void on_change(Thing* thing) = 0;
    };
    void add_observer(Observer* observer);
    void remove_observer(Observer* observer);

    void notify_observers();
private:
    typedef std::vector<Observer*> Observers;
    Observers observers;
};

void Thing::notify_observers() {

    /* going backwards through a vector allows the current item to be removed in
    the callback, but it can't cope with not-yet-called observers being removed */
    for(int i=observers.size()-1; i>=0; i--)
        observers[i]->on_change(this);

// OR is there another way using something more iterator-like?

    for(Observers::iterator i=...;...;...) {
        (*i)->on_change(this); //<-- what if the Observer implementation calls add_ or remove_ during its execution?
    }
}

我可能有一个由 add_ 和 remove_ 设置的标志来重置我的迭代器,如果它变得无效,然后可能在每个观察者中设置一个“生成”计数器,这样我就知道我是否已经调用它了?

4

6 回答 6

3

也许您可以使用更好的(?)设计。例如,您可以让通知函数根据它们的返回值将它们删除(或执行任何其他操作),而不是让观察者自己删除。

于 2009-06-08T22:05:32.317 回答
2

添加或插入项目是否会使某些迭代器无效到容器中完全取决于容器类型。

您可能需要进行调查std::list,因为这是对迭代器验证更宽容的容器之一。例如,在移除一个元素时,只有指向被移除元素的迭代器才会失效。所有其他迭代器仍然有效。

您仍然需要确定哪种操作是有效的。您可以考虑不允许在观察者列表上直接添加/删除操作,并在发生通知时排队添加和删除操作,在通知完成时对队列进行操作。

如果只允许观察者删除自己或添加新的观察者,这可能是矫枉过正,这样的循环将足够安全:

for( std::list<Observer>::iterator i = observers.begin(); i != observers.end(); )
{
    std::list<Observer>::iterator save = i++;
    save->on_change();
}
于 2009-06-08T21:59:40.237 回答
1

拥有不会失效的迭代器的最简单方法是将您的观察者存储在列表中而不是向量中。列表迭代器不会因添加或删除项目而失效,除非它们指向被删除的项目。

如果你想坚持使用向量,我能想到的最好的方法是在添加项目时重置一个标志(添加可以使向量中的每个项目无效),然后使用预减循环去通过向量(因为删除只会使该点之后的项目无效,而不是之前的项目)。

于 2009-06-08T22:02:14.240 回答
1

管理这种混乱的明智方法是设置一个标志,以便删除代码知道它是否在迭代观察者。

在remove中,如果代码在一个迭代中,那么指针被设置为null而不是remove。该标志设置为第三状态以指示这已经发生。

观察者必须使用 [] 运算符进行迭代,以防在迭代期间调用 add 并重新分配数组。数组中的空值被忽略。

迭代后,如果设置标志以指示在迭代中删除了观察者,则可以压缩数组。

于 2009-11-25T11:33:47.003 回答
0

如果不使指向或超出已删除项的任何迭代器失效,就无法安全地从向量中添加和删除项。如果这对您来说是个问题,也许您应该使用不同的容器?您可以添加和删除列表或地图,仅在受影响的位置使迭代器无效。

您可以使用以下方法进行迭代。由于我们正在制作副本,因此它允许在容器中任意插入和删除:

void Thing::notify_observers()
{
   Observers obscopy=observers;
   Observers::iterator i=obscopy.begin();
   while (i!=obscopy.end())
   {
       (*i)->on_change(this);
       ++i;
   }
}
于 2009-06-08T22:01:12.793 回答
0

我认为你与几代人走在正确的轨道上。您的问题尚不清楚是否需要将观察者的更改应用于当前通知。如果不是,那么我会将所有需要继续应用到下一代的观察者移到下一代,而不要理会当前的迭代器。

于 2009-06-08T22:10:34.147 回答