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



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

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

如果在thingHappened的实现中,观察者调用SomeClass的方法将自己从观察者中删除,该怎么办?处理此问题的最佳方法是什么?

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

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

如果你可以控制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());
}

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

如果在 thingHappen 的实现中,观察者在 SomeClass 中调用一个方法来将自己从观察者中删除怎么办?处理此问题的最佳方法是什么?

以下方法过去对我有用。

  1. 请注意,您将迭代观察器。
  2. 当客户端请求删除要删除的观察程序时,请检查您是否正在迭代观察程序。如果是,请将其放在另一个向量中。如果没有,请将其从观察者中删除。
  3. 循环访问观察程序后,删除所有需要删除的观察程序。
  4. 请注意,您已完成对观察器的迭代。

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

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

以下是您可以执行的操作的草图:

#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 1n";
thing->remove(this);
}
};
int main()
{
SomeClass thing;
Observer1 obs;
obs.thing = &thing;
thing.addObserver(&obs);
thing.doSomething();
thing.doSomething();
}

在科里鲁

最新更新