for(auto e:elements)可能导致某个元素被从vector中删除

时间:2014-12-21 01:35:44

标签: c++ c++11 vector

我有一些课程:

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>

5 个答案:

答案 0 :(得分:3)

如果在迭代时向量可能会被更改,那么只有一种方法可以去:

迭代副本!

当然,除非你可以改变数据结构。

答案 1 :(得分:2)

这似乎是std::list的可能用例,其中迭代器在列表更改时不会失效(除非它们引用的东西被删除)。

如果您有std::list<Listener*>,则可以使用两个迭代器currentnext(例如)逐步查看列表,将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开头的funfalse末尾的fun

removeListener内,检查标志。如果是false,只需像现在一样删除侦听器。否则,将侦听器添加到将来要删除的第二个侦听器向量中。

fun的最后,从所述侦听器向量中删除所有侦听器。然后清除所说的载体。

如果fun需要重新进入,请使用int代替布尔标记并向上/向下计数。