SeniorДебаггингИногдаЕщё не отвечали
Почему наивная реализация GoF Observer становится опасной для времени жизни и многопоточности?
Эта наивная реализация GoF Observer работает в тестах, но в продакшене падает, когда наблюдатель уничтожается без отписки, и ломается, когда колбэк подписывается или отписывается во время notify().
class Subject {
public:
void subscribe(Observer* o) { observers_.push_back(o); }
void unsubscribe(Observer* o) { /* erase-remove ... */ }
void notify() {
for (Observer* o : observers_)
o->update();
}
private:
std::vector<Observer*> observers_;
};
Найдите ошибку и объясните причину.
Субъект хранит сырые указатели на наблюдателей, которыми не владеет, поэтому уничтоженный, но не отписанный наблюдатель превращается в висячий вызов. Реентрантный notify() — наблюдатель подписывается или уничтожает себя внутри своего колбэка — инвалидирует итератор посреди цикла. Решение: weak_ptr на наблюдателей, копия-снимок списка перед обходом и отложенные add/remove.
- ✗Считать, что наблюдатель всегда живёт дольше субъекта — в реальном коде наблюдатели уничтожаются первыми и оставляют висячий указатель в списке
- ✗Обходить живой контейнер наблюдателей напрямую, из-за чего наблюдатель, отписавшийся внутри своего колбэка, инвалидирует итератор
- ✗Удерживать мьютекс субъекта на весь цикл
notify(), из-за чего колбэк наблюдателя, вызывающий субъект, приводит к самодедлоку
- →Как копия-снимок списка наблюдателей взаимодействует с наблюдателем, отписывающимся во время notify()?
- →Почему
weak_ptrпредпочтительнее явного контракта 'отписка в деструкторе'?
Наивный GoF Observer — где он ломается
#include <vector>
struct Observer {
virtual ~Observer() = default;
virtual void update() = 0;
};
class Subject {
public:
void subscribe(Observer* o) { observers_.push_back(o); } // сырой указатель — субъект не владеет
void unsubscribe(Observer* o) { /* erase-remove ... */ }
void notify() {
for (Observer* o : observers_) // ⚠️ обход живого контейнера
o->update(); // ❌ висячий вызов, если o уже уничтожен;
// ❌ если update() вызовет subscribe()/unsubscribe(),
// итератор инвалидируется посреди цикла
}
private:
std::vector<Observer*> observers_; // сырые указатели на чужие объекты
};
// ✅ Безопасная форма
#include <algorithm>
#include <memory>
#include <vector>
class SafeSubject {
public:
void subscribe(std::weak_ptr<Observer> o) { observers_.push_back(std::move(o)); }
void notify() {
auto snapshot = observers_; // копия-снимок: переживёт мутации списка
for (auto& w : snapshot)
if (auto o = w.lock()) // weak_ptr -> жив ли наблюдатель?
o->update(); // отложенные add/remove применяются после цикла
std::erase_if(observers_, [](auto& w) { return w.expired(); });
}
private:
std::vector<std::weak_ptr<Observer>> observers_;
};