迭代std :: vector(间接)

时间:2017-07-17 19:50:19

标签: c++ vector

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

void SomeClass::doThing() {
    // do things ...
    // notify observers
    for (auto* o : mObservers) {
        o->thingHappened();
    }
}

如果在thingHappened的实现中,观察者调用SomeClass中的方法将其自身从观察者中删除,该怎么办?有哪些最佳方法可以解决这个问题?

一种可能性是在for循环之前复制mObservers并使用它,但额外的副本可能会浪费。

另一种可能性是在循环结束后将更改委托给要运行的数组,也许在循环开始之前设置一个锁(只是一个布尔值),并且在设置此锁定时,变换向量的方法将自己委托给在lock设置为false时完成循环后调用(可以使用lambdas向量完成...非常麻烦)。

4 个答案:

答案 0 :(得分:7)

如果您可以控制thingHappened()的签名,则可以将其更改为返回bool,指示是否应将其删除。然后,您可以删除返回true(或false;取决于您想要的语义)的所有值。

幸运的是,std::remove_ifstd::partition保证在范围内每个对象只调用一次谓词。

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());
}

答案 1 :(得分:5)

解决此问题的一种方法是更改​​数据结构。使用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;
}

答案 2 :(得分:3)

  

如果在thingHappened的实现中观察者调用SomeClass中的一个方法从观察者中移除自己怎么办?有哪些最佳方法可以解决这个问题?

以下方法对我有用。

  1. 请注意,您将迭代观察者。
  2. 当客户端请求删除要删除的观察者时,请检查您是否正在迭代观察者。如果你是,请将它放在另一个向量中。如果没有,请将其从观察者处移除。
  3. 完成对观察者的迭代后,删除所有需要删除的观察者。
  4. 请注意,您已完成对观察者的迭代。
  5. void SomeClass::removeObserver(Observer* o) {
       if ( this->isIterating  )
       {
          observersToRemove.push_back(o);
       }
       else
       {
          // Code for real removal of the observer
       }
    }
    
    void SomeClass::doThing() {
       this->isIterating = true;
       for (auto* o : mObservers) {
          o->thingHappened();
       }
    
       for ( auto* o : observersToRemove )
       {
          // Code for real removal of the observer
       }
    
       observersToRemove.clear();
    
       this->isIterating = false;
    }
    

答案 3 :(得分:2)

R Sahu's answer为解决此问题提供了灵活的技术。关注它的一件事是引入了几个必须管理的变量。但是,完全可以将功能包装在实用程序类中。

以下是您可以做的草图:

#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); }
};

使用它看起来像这样:

#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();
}

On Coliru