I am writing an Observer design pattern where Subject knows what Observer it has (same as the original design) and Observer also knows what Subject it's attached to, mainly to tackle scenarios like Observer going out of scope and Subject has a reference to a destroyed object.
So if an Observer watches a Subject, both know about each other. This is handled via observer->removeSubject and observer->addSubject.
In the following snippet, I am testing a case where Observers go out of scope, meaning ~Observer() gets invoked first however I seem to be getting a segfault on the following line when I debug in an online gdb tool in the second iteration of ~Observer()
std::cout << "Observers size = " << _observers.size() << "\n";
template<typename T>
class Subject;
template<typename T>
class Observer
{
std::set<Subject<T>*> _subjects;
public:
virtual void update(T val) = 0;
void addSubject(Subject<T>* subject)
{
printf ("[Observer] Adding Subject to observer - ");
_subjects.insert(subject);
std::cout << "Subject size = " << _subjects.size() << "\n\n";
}
void removeSubject(Subject<T>* subject)
{
printf ("[Observer] Removing Subject from observer - ");
_subjects.erase(subject);
std::cout << "Subject size = " << _subjects.size() << "\n";
}
virtual ~Observer()
{
printf ("\n~Observer\nSubject Size = %ld\n", _subjects.size());
for (auto& subject : _subjects)
{
subject->detach(this);
}
}
};
template<typename T>
class Subject
{
std::set<Observer<T>*> _observers;
protected:
Subject() = default;
public:
void attach(Observer<T>* observer)
{
printf ("~~ [Subject] Attaching observer ~~\n");
_observers.insert(observer);
observer->addSubject(this);
}
void detach(Observer<T>* observer)
{
printf ("~~ [Subject] Detaching observer ~~\n");
std::cout << "Observers size = " << _observers.size() << "\n";
_observers.erase(observer);
observer->removeSubject(this);
}
void notify(const T& val)
{
for (auto& observer : _observers)
{
observer->update(val);
}
}
virtual ~Subject()
{
printf ("\n~Subject\n");
printf ("Observer size = %ld\n", _observers.size());
for (auto& obs : _observers)
{
obs->removeSubject(this);
}
}
};
template<typename T>
class ConcreteSubject : public Subject<T>
{
T _value;
public:
void set(const T& value)
{
_value = value;
this->notify(value);
}
};
template<typename T>
class ConcreteObserver : public Observer<T>
{
public:
void update(T value)
{
std::cout << "Observer Notified: " << value << "\n";
}
};
int main()
{
ConcreteSubject<int> sub;
{
ConcreteObserver<int> obs;
ConcreteObserver<int> obs1;
sub.attach(&obs);
sub.attach(&obs1);
sub.set(5);
}
}
One part that I am concerned about is the following where subject is removed from _subjects while _subjects is being iterated over.
Should a copy of _subjects be made to be iterated over in ~Observer() instead?
void removeSubject(Subject<T>* subject)
{
printf ("[Observer] Removing Subject from observer - ");
_subjects.erase(subject);
std::cout << "Subject size = " << _subjects.size() << "\n";
}
virtual ~Observer()
{
printf ("\n~Observer\nSubject Size = %ld\n", _subjects.size());
for (auto& subject : _subjects)
{
subject->detach(this);
}
}