C++ 在迭代std::vector时删除(间接)

C++ 在迭代std::vector时删除(间接),c++,vector,C++,Vector,这个问题已经被问了很多次,但我的情况略有不同。假设我有一个观察者的std::vector,当某个事件发生时,我会通知它: void SomeClass::doThing() { // do things ... // notify observers for (auto* o : mObservers) { o->thingHappened(); } } 如果在ThingOccessed的实现中,观察者调用SomeClass中的一个方法将自

这个问题已经被问了很多次,但我的情况略有不同。假设我有一个观察者的std::vector,当某个事件发生时,我会通知它:

void SomeClass::doThing() {
    // do things ...
    // notify observers
    for (auto* o : mObservers) {
        o->thingHappened();
    }
}
如果在
ThingOccessed
的实现中,观察者调用
SomeClass
中的一个方法将自己从观察者中移除,该怎么办?处理这个问题的最好方法是什么

一种可能是在for循环之前复制一个
mObservers
,然后使用它,但是额外的副本可能会造成浪费


另一种可能是将更改委托给循环完成后要运行的数组,可能在循环开始之前设置一个锁(只是一个布尔值),并且在设置此锁时,当lock设置为false时,将向量委托本身变异为在循环完成后调用的方法(可以使用lambda向量来完成…相当麻烦)。

如果您可以控制
thingOccessed()
的签名,您可以将其更改为返回一个
bool
,指示是否应该删除它。然后,您可以删除所有返回
true
(或
false
;取决于所需的语义)

幸运的是,我们可以保证在范围内的每个对象只调用一次谓词

void SomeClass::doThing() {
    // do things ...
    // notify observers
    auto newEnd = std::remove_if(mObservers.begin(), mObservers.end(), [](auto *o) {
        return o->thingHappened();
    });
    // assuming mObservers is a vector
    mObservers.erase(newEnd, mObservers.end());
}

解决此问题的一种方法是更改数据结构。对于
std::list
,删除元素只会使指向该元素的迭代器/引用/指针无效。由于列表的其余部分保持不变,所以我们需要做的就是在处理当前元素之前,获得指向下一个元素的迭代器

for (auto it = the_list.begin(); it != the_list.end();)
{
    auto next = std::next(it);
    it->call_the_possibly_removing_function();
    it = next;
}
如果在事件发生的实现中,观察者调用某个类中的一个方法来从观察者中移除自己,该怎么办?处理这个问题的最佳方法是什么

以下方法在过去对我很有效

  • 请注意,您将迭代观察者
  • <> li>当客户端请求删除一个观察者要被移除时,检查你是否在迭代观察者中间。如果你是,把它放在另一个向量中。如果不是,把它从观察者那里删除。
  • 在完成对观察者的迭代后,删除所有需要删除的观察者
  • 注意,您已经完成了对观察者的迭代

  • 为解决此问题提供了一种灵活的技术。我关心的一件事是引入了几个必须管理的变量。但是,完全可以将功能包装到实用程序类中

    下面是您可以做的一个示意图:

    #include <functional>
    #include <utility>
    #include <vector>
    
    // Note that this is not threadsafe
    template <typename Type>
    class MutableLock {
        bool locked = false;
        Type value;
        // std::function gives us a more general action,
        // but it does come at a cost; you might want to consider using
        // other techniques.
        std::vector<std::function<void(Type&)>> actions;
    
    public:
        class AutoLocker {
            MutableLock& lock;
    
            friend class MutableLock<Type>;
    
            explicit AutoLocker(MutableLock& lock)
                : lock{ lock }
            {
            }
    
        public:
            ~AutoLocker()
            {
                lock.unlock();
            }
        };
    
        MutableLock() = default;
    
        // The [[nodiscard]] is a C++17 attribute that
        // would help enforce using this function appropriately
        [[nodiscard]] AutoLocker lock()
        {
            locked = true;
            return AutoLocker{ *this };
        }
    
        void unlock()
        {
            for (auto const& action : actions) {
                action(value);
            }
            actions.clear();
    
            locked = false;
        }
    
        template <typename F>
        void action(F&& f)
        {
            if (!locked) {
                f(value);
            } else {
                actions.emplace_back(std::forward<F>(f));
            }
        }
    
        // There needs to be some way to expose the value
        // not under the lock (so that we can use it when
        // we call `lock()`).
        //
        // Even if your `Type` is not a range, this would
        // be fine, as member functions of a template class
        // aren't instantiated unless you call them.
        //
        // However, you may want to expose other ways to
        // access the value
        auto begin() { return std::begin(value); }
        auto end() { return std::end(value); }
        auto begin() const { return std::begin(value); }
        auto end() const { return std::end(value); }
    };
    
    #包括
    #包括
    #包括
    //请注意,这不是线程安全的
    模板
    类可变锁{
    布尔锁定=假;
    类型值;
    //函数为我们提供了更一般的操作,
    但是它确实是有代价的;你可能想考虑使用。
    //其他技术。
    向量作用;
    公众:
    类自动记录器{
    可变锁&lock;
    友元类可变锁;
    显式自动锁定(可变锁定和锁定)
    :lock{lock}
    {
    }
    公众:
    ~AutoLocker()
    {
    lock.unlock();
    }
    };
    MutableLock()=默认值;
    //[[nodiscard]]是一个C++17属性,它
    //将有助于强制适当地使用此功能
    [[nodiscard]]AutoLocker锁()
    {
    锁定=真;
    返回AutoLocker{*this};
    }
    无效解锁()
    {
    用于(自动常量和操作:操作){
    作用(价值);
    }
    动作。清除();
    锁定=错误;
    }
    模板
    无效操作(F&F)
    {
    如果(!已锁定){
    f(价值);
    }否则{
    行动:向后(标准::向前(f));
    }
    }
    //需要有某种方法来公开该值
    //不在锁下(以便我们可以在
    //我们称之为‘lock()`)。
    //
    //即使您的'Type'不是一个范围,这也会
    //可以,作为模板类的成员函数
    //除非调用它们,否则不会实例化。
    //
    //但是,您可能希望以其他方式向
    //访问值
    自动开始(){return std::begin(value);}
    自动结束(){return std::end(value);}
    自动开始()常量{return std::begin(value);}
    auto end()常量{return std::end(value);}
    };
    
    使用它看起来像这样:

    #include <algorithm>
    #include <iostream>
    
    class Observer {
    public:
        virtual void thingHappened() = 0;
    
    protected:
        ~Observer() = default;
    };
    
    class SomeClass {
        MutableLock<std::vector<Observer*>> observers;
    
    public:
        void addObserver(Observer* observer)
        {
            observers.action([observer](auto& observers) {
                observers.push_back(observer);
            });
        }
    
        void remove(Observer const* observer)
        {
            observers.action([observer](auto& observers) {
                observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
            });
        }
    
        void doSomething()
        {
            auto lock = observers.lock();
            for (auto* observer : observers) {
                observer->thingHappened();
            }
            // when `lock` goes out of scope, we automatically unlock `observers` and
            // apply any actions that were built up
        }
    };
    
    class Observer1 : public Observer {
    public:
        SomeClass* thing;
    
        void thingHappened() override
        {
            std::cout << "thing 1\n";
            thing->remove(this);
        }
    };
    
    int main()
    {
        SomeClass thing;
        Observer1 obs;
        obs.thing = &thing;
    
        thing.addObserver(&obs);
        thing.doSomething();
        thing.doSomething();
    }
    

    #include

    你已经描述了我能想到的处理这种情况的唯一两种方法。你希望有第三种方法吗?@xissburg复制一个
    观察者的成本有多高?因为如果它是琐碎或便宜的,那么它肯定是值得的。@xissburg这是一个相对便宜的副本,那么。只要制作一个副本。我们在谈论多少观察者?如果不是很多,你可以使用
    std::list
    ,很容易地绕过这个问题。@markransem你不能,但你可以先找到下一个迭代器。我正在写一个答案。我想我应该更改这个问题的标题。这不仅是关于删除,还涉及到对向量。可能有多个突变。@西斯堡那将是一个单独的问题。我使用这种方法的主要问题是有更多的变量浮动,但额外的灵活性可能会使这一点变得有价值。可以将其中的大部分内容封装在一个对象中,以便更容易地跟踪发生了什么ng@Justin,正如您从三个不同的答案中所看到的,它们是解决问题的不止一种方法。使用您觉得最合适的方法。@Justin,是的,它确实使其他函数变得更复杂,但您拥有正确实现
    所需的所有数据。
    。您必须检查输入是否正确首先在
    mObservers
    中。如果它在那里,您需要检查它是否在
    observersToRemove
    中@
    #include <algorithm>
    #include <iostream>
    
    class Observer {
    public:
        virtual void thingHappened() = 0;
    
    protected:
        ~Observer() = default;
    };
    
    class SomeClass {
        MutableLock<std::vector<Observer*>> observers;
    
    public:
        void addObserver(Observer* observer)
        {
            observers.action([observer](auto& observers) {
                observers.push_back(observer);
            });
        }
    
        void remove(Observer const* observer)
        {
            observers.action([observer](auto& observers) {
                observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
            });
        }
    
        void doSomething()
        {
            auto lock = observers.lock();
            for (auto* observer : observers) {
                observer->thingHappened();
            }
            // when `lock` goes out of scope, we automatically unlock `observers` and
            // apply any actions that were built up
        }
    };
    
    class Observer1 : public Observer {
    public:
        SomeClass* thing;
    
        void thingHappened() override
        {
            std::cout << "thing 1\n";
            thing->remove(this);
        }
    };
    
    int main()
    {
        SomeClass thing;
        Observer1 obs;
        obs.thing = &thing;
    
        thing.addObserver(&obs);
        thing.doSomething();
        thing.doSomething();
    }