这个问题已被多次询问,但我的情况略有不同。假设我有一个观察者的std :: vector,当某个事件发生时我会通知它:
void SomeClass::doThing() {
// do things ...
// notify observers
for (auto* o : mObservers) {
o->thingHappened();
}
}
如果在thingHappened
的实现中,观察者调用SomeClass
中的方法将其自身从观察者中删除,该怎么办?有哪些最佳方法可以解决这个问题?
一种可能性是在for循环之前复制mObservers
并使用它,但额外的副本可能会浪费。
另一种可能性是在循环结束后将更改委托给要运行的数组,也许在循环开始之前设置一个锁(只是一个布尔值),并且在设置此锁定时,变换向量的方法将自己委托给在lock设置为false时完成循环后调用(可以使用lambdas向量完成...非常麻烦)。
答案 0 :(得分:7)
如果您可以控制thingHappened()
的签名,则可以将其更改为返回bool
,指示是否应将其删除。然后,您可以删除返回true
(或false
;取决于您想要的语义)的所有值。
幸运的是,std::remove_if
和std::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中的一个方法从观察者中移除自己怎么办?有哪些最佳方法可以解决这个问题?
以下方法对我有用。
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();
}