代码之家  ›  专栏  ›  技术社区  ›  Antonio Dropulić

无交互引用和智能指针的观察者模式实现

  •  0
  • Antonio Dropulić  · 技术社区  · 7 年前

    我正在尝试实现观察家模式,但我不希望观察家通过在中维护引用列表来对我的程序安全负责 ObservableSubject

    意思是当 Observer object 生命结束了,我不想显式地调用 ObservervableSubject::removeObserver(&object)

    我想出了一个在 可观察主题

    我的问题是:上面描述的和下面尝试的实现是否可能? 我的程序中发生了什么,如何防止取消引用垃圾?

    先验理由:这是一种理解C++的尝试,而不是应该有实际用途或优于其他实现的东西。

    我的解决方案尝试:

    // Example program
    #include <iostream>
    #include <string>
    #include <vector>
    
    class ObserverInterface {
    public:
        virtual ~ObserverInterface() {};
        virtual void handleMessage() = 0;
    };
    
    class ObservableSubject
    {
        std::vector<std::reference_wrapper<ObserverInterface*>> listeners;
    
    public:
        void addObserver(ObserverInterface* obs)
        {
            if (&obs)
            {
                // is this a reference to the copied ptr?
                // still, why doesnt my guard in notify protect me
                this->listeners.push_back(obs);
            }
        }
    
        void removeObserver(ObserverInterface* obs)
        {
            // todo
        }
    
        void notify()
        {
            for (ObserverInterface* listener : this->listeners)
            {
                if (listener)
                {
                    listener->handleMessage();
                }
            }
        }
    };
    
    class ConcreteObserver : public ObserverInterface {
        void handleMessage()
        {
            std::cout << "ConcreteObserver: I'm doing work..." << std::endl;
        }
    };
    
    int main()
    {
        ObservableSubject o;
    
        {
            ConcreteObserver c;
            o.addListener(&c);
        }
    
        o.notify();
    
        std::cin.get();
    }
    

    线路输入 ObservableSubject::notify() : Listener->handleMessage() 引发以下异常:

    Exception thrown: read access violation.
    listener->**** was 0xD8BF48B. occurred
    
    3 回复  |  直到 7 年前
        1
  •  5
  •   R Sahu    7 年前

    您的程序具有未定义的行为。

    ObservableSubject o;
    
    {
        ConcreteObserver c;
        o.addListener(&c);  // Problem
    
    }
    

    c 在作用域结束时被销毁。最后,在的侦听器列表中存储了一个过时的指针 o

    您可以通过定义 C 在与相同的范围内 o 或者使用动态分配的内存。

    ObservableSubject o;
    ConcreteObserver c;
    o.addListener(&c);
    

    ObservableSubject o;
    
    {
        ConcreteObserver* c = new ConcreteObserver;
        o.addListener(c);
    }
    

    当您使用动态分配的内存时,额外的作用域没有用处。你最好不要用它。

    ObservableSubject o;
    ConcreteObserver* c = new ConcreteObserver;
    o.addListener(c);
    

    如果选择使用第二种方法,请确保释放内存。您需要添加

    delete c;
    

    函数结束之前。


    更新,回应OP的评论

    你说:

    也许我不清楚。解决生存期/陈旧指针问题是我解决方案的目的。我知道,如果我能妥善管理自己的一生,或者如果我能 detachObserver 观察员销毁选项。我想通过某种方式从 ObservableSubject 如果他的观察员名单被破坏了,而观察员没有明确告诉他。

    由于取消引用无效指针是导致未定义行为的原因,因此必须跟踪观察者的生存期,并确保在必要时更新观察者列表。没有这些,你就是在追求未定义的行为。

        2
  •  3
  •   Andrew Lazarus    7 年前

    注意,我不推荐以下方法,但我认为它符合您的要求。您有一个重复的观察者列表。一个由观察者控制,另一个使用弱指针,由可观察对象处理。

    1. 使观察器构造函数私有并使用 ObserverFactory (这是他们的朋友)获得 std::shared_ptr<Observer> 。工厂具有从原始指针到引用包装器到关联共享指针的映射。
    2. 侦听器列表变为 std::vector<std::weak_ptr<Observer>> 。在列表遍历时,您尝试锁定弱ptr;如果成功,则处理消息;如果失败,也就是说 nullptr ,从列表中删除弱指针。
    3. 当监听器不再想听时,它会告诉工厂 reset 并从映射中删除。这一步很难看,因为它只是一种幻想 delete this ,通常是代码气味。

    我相信你也可以用 std::shared_from_this

    计划是将维护从ObservableSubject移回Observators。

        3
  •  2
  •   besc    7 年前

    //这是对复制的ptr的引用吗?

    是的,是的。它调用未定义的行为,因为 obs 指针变量在函数末尾超出范围,导致引用悬空。

    整个想法对你毫无益处。即使您使ref-to-pointer方法正确工作,您也取决于一件事:那就是 那正是 指针变量设置为 nullptr 一旦对象死亡。本质上,这与确保没有挂起的指针保持在 listeners

    对于堆对象:如何确保没有人通过其他指针删除对象?或者忘记将注册的指针设为null?对于像您的示例中那样的堆栈对象,情况甚至更糟。对象超出范围并自动消亡。除非引入一个必须手动管理的额外指针变量,否则没有机会将任何内容设为null。

    您可以考虑两种方法的一般替代方案:

    • 使关系具有双向性。然后,无论谁先死亡(可观察者或观察者),都可以在析构函数中通知另一方其死亡。
    • 如果您不喜欢双向性,那么一个将OBERSERVER和observables解耦的中心、无所不知的编排器也可以工作。当然,这带来了某种全球状态。

    现实生活中的实现通常朝着利用C++析构函数取消注册的方向发展。例如,看看Qts信号/插槽机制。