添加和删​​除项而不使迭代器失效

时间:2009-06-08 21:54:59

标签: c++ iterator std

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

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

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_设置,如果它失效则重置我的迭代器,然后在每个观察者中可能是一个“生成”计数器,所以我知道我是否已经调用它?

6 个答案:

答案 0 :(得分:3)

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

答案 1 :(得分:2)

添加或插入项目是否会使某些项目失效,所有迭​​代器都完全依赖于容器类型。

您可能想要调查std::list,因为这是与迭代器验证相关的容忍度更高的容器之一。例如,在删除元素时,只有指向已删除元素的迭代器才会失效。所有其他迭代器仍然有效。

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

如果只允许观察者移除自己或添加新的观察者,这可能是过度的,这样的循环就足够安全了:

for( std::list<Observer>::iterator i = observers.begin(); i != observers.end(); )
{
    std::list<Observer>::iterator save = i++;
    save->on_change();
}

答案 2 :(得分:1)

使迭代器不会失效的最简单方法是将Observers存储在列表中而不是矢量中。列表迭代器不会通过添加或删除项目而失效,除非它们指向要删除的项目。

如果你想坚持使用矢量,我可以立即想到的最好方法是在添加项目时添加一个标志(添加可以使向量中的每个项无效),然后使用预减量循环遍历向量(因为删除只会使点之后的项无效,从不在它之前)。

答案 3 :(得分:1)

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

在remove中,如果代码在迭代中,则指针设置为null而不是删除。该标志设置为第三个状态,表示发生了这种情况。

如果在迭代期间调用add,则必须使用[]运算符迭代观察者,并重新分配该数组。数组中的空值将被忽略。

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

答案 4 :(得分:0)

您不能安全地添加和删除矢量中的项目,而不会使指向或移出已删除项目的任何迭代器失效。如果这对你来说是个问题,也许你应该使用不同的容器?您可以添加和删除列表或映射,只会使受影响位置的迭代器无效。

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

void Thing::notify_observers()
{
   Observers obscopy=observers;
   Observers::iterator i=obscopy.begin();
   while (i!=obscopy.end())
   {
       (*i)->on_change(this);
       ++i;
   }
}

答案 5 :(得分:0)

我认为你们几代人都走在了正确的轨道上。你的问题不清楚的是观察者的变化是否需要应用于当前的通知。如果没有,那么我会移动所有需要继续应用于下一代的观察者并单独留下当前的迭代器。