You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

实现双向关联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的副本?

分析与解决方案

段错误的根本原因

问题出在双向删除时的容器迭代器失效:

  1. Observer析构时,遍历_subjects并调用subject->detach(this)
  2. detach方法中会执行observer->removeSubject(this),直接从当前Observer的_subjects集合中删除正在遍历的subject
  3. 原遍历迭代器指向的元素已被移除,继续迭代会导致迭代器失效,触发段错误

解决办法:遍历容器的副本

你的担忧完全正确,必须遍历_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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 15:20:42