实现双向关联Observer Design Pattern时遭遇段错误的问题排查
问题
我正在实现一种双向关联的观察者模式:Subject持有关联的Observer引用,同时Observer也持有关联的Subject引用,目的是解决Observer超出作用域销毁后,Subject仍持有已销毁对象引用的问题。关联关系通过observer->removeSubject和observer->addSubject维护。
测试Observer超出作用域的场景时,~Observer()会先被调用,但在线gdb调试中,第二次迭代时以下代码触发段错误:
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); } }
我现在担忧的是:在~Observer()中遍历_subjects时,subject会被从_subjects中移除,是不是应该遍历_subjects的副本?
分析与解决方案
段错误的根本原因
问题出在双向删除时的容器迭代器失效:
- Observer析构时,遍历
_subjects并调用subject->detach(this) detach方法中会执行observer->removeSubject(this),直接从当前Observer的_subjects集合中删除正在遍历的subject- 原遍历迭代器指向的元素已被移除,继续迭代会导致迭代器失效,触发段错误
解决办法:遍历容器的副本
你的担忧完全正确,必须遍历_subjects的副本,避免遍历过程中修改原容器导致迭代器失效。修改~Observer()如下:
virtual ~Observer() { printf ("\n~Observer\nSubject Size = %ld\n", _subjects.size()); // 遍历副本,避免原容器被修改导致迭代器失效 auto subjectsCopy = _subjects; for (auto& subject : subjectsCopy) { subject->detach(this); } }
额外优化建议
- 在
removeSubject和detach中添加元素存在性检查,避免重复删除操作 - 考虑用
std::weak_ptr替代裸指针,更安全地管理对象生命周期,从根源上避免悬垂指针问题,这是C++处理双向关联场景更优雅的方案
内容的提问来源于stack exchange,提问作者xyf
相关产品推荐
相关产品推荐

