没有互惠引用和智能指针的观察者模式实现

时间:2018-04-05 20:20:38

标签: c++ c++11 observer-pattern

我试图实施观察者模式,但我不希望观察者通过维护ObservableSubject中的参考列表来对我的程序安全负责。

这意味着当Observer object生命周期结束时,我不想明确调用ObservervableSubject::removeObserver(&object)

我想出了在ObservableSubject中使用use指针引用的想法。

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

Apriori借口:这是尝试理解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 个答案:

答案 0 :(得分:5)

您的程序有不确定的行为。

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的评论

你说:

  

也许我不清楚。解决生命周期/陈旧指针问题是我的解决方案的目的。我知道如果我有正确的托管生命周期,或者如果我在Observer destroy上添加detachObserver选项,我就没有问题。我希望以某种方式能够告诉ObservableSubject他的观察者列表是否已损坏,而Observer没有明确说明。

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

答案 1 :(得分:2)

  

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

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

整个想法并没有给你带来任何好处。即使你使ref-to-pointer方法正常工作,你依赖于一件事:一旦对象死亡,那个精确的指针变量被设置为nullptr。基本上这与确保listeners中没有悬空指针的问题相同。

对于堆对象:如何确保没有人通过不同的指针删除对象?或者忘记将已注册的指针置空?对于像您的示例中的堆栈对象来说,情况更糟。该对象超出范围并自动死亡。除非你引入了一个你必须手动管理的附加指针变量,否则没有机会取消任何内容。

您可以考虑两种常用的替代方法:

  • 使关系双向化。然后,任何先死亡的人(可观察的或观察者)都可以通知另一方它在析构者中的死亡。
  • 如果您不喜欢双向性,那么将观察者和观察者分离的中心,无所不知的协调者也可以。当然,这会引入某种全球状态。

实际实现通常是利用C ++析构函数进行注销的总体方向。例如,看看Qt的信号/插槽机制。

答案 2 :(得分:2)

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

  1. 将Observer构造函数设为私有,并使用ObserverFactory(这是他们的朋友)获取std::shared_ptr<Observer>。工厂有一个从原始指针到引用包装器到相关共享指针的映射。
  2. 听众列表变为std::vector<std::weak_ptr<Observer>>。在列表遍历上,您尝试锁定weak_ptr;如果成功,处理消息;如果它失败了,就是你得到nullptr,从列表中删除弱指针。
  3. 当监听器不再想要监听时,它会告诉Factory在其共享指针上执行reset并从地图中删除。这个步骤相当丑陋,因为它只是一个花哨的delete this,通常是代码味道。
  4. 我相信您也可以使用std::shared_from_this

    执行此操作

    计划是将维护从ObservableSubject移回观察者。