【问题标题】:Using weak_ptr to implement the Observer pattern使用weak_ptr实现观察者模式
【发布时间】:2017-01-23 19:02:09
【问题描述】:

到目前为止我所拥有的是:

观察者.h

class Observer
{
public:
    ~Observer();
    virtual void Notify() = 0;
protected:
    Observer();
};

class Observable
{
public:
    ~Observable();
    void Subscribe( std::shared_ptr<Observer> observer );
    void Unsubscribe( std::shared_ptr<Observer> observer );
    void Notify();
protected:
    Observable();
private:
    std::vector<std::weak_ptr<Observer>> observers;
};

观察者.cpp

void Observable::Subscribe( std::shared_ptr<Observer> observer )
{
    observers.push_back( observer );
}

void Observable::Unsubscribe( std::shared_ptr<Observer> observer )
{
    ???
}

void Observable::Notify()
{
    for ( auto wptr : observers )
    {
        if ( !wptr.expired() )
        {
            auto observer = wptr.lock();
            observer->Notify();
        }
    }
}

(de/constructors 在这里实现但为空,所以我把它们省略了)

我坚持的是如何实现取消订阅过程。我遇到了擦除 - 删除 - 结束习语,但我知道它不会在我设置 Observable 时“开箱即用”。如何检查观察者向量中的 weak_ptr 元素,以便删除所需的观察者?

我也在寻找一些关于我的 Un/Subscribe 程序的参数类型应该是什么的建议。使用std::shared_ptr&lt;Observer&gt;&amp;const std::shared_ptr&lt;Observer&gt;&amp; 会更好吗,因为我们不会对其进行修改?

我真的不想让 Observables 拥有他们的 Observers,因为这似乎背叛了该模式的意图,当然也不是我想要构建最终将使用该模式的项目的其余部分的方式。也就是说,我正在考虑增加一层安全/自动化是让 Observers 存储weak_ptr 的镜像向量。一个离开的 Observer 可以取消订阅它已经订阅的所有 Observable,一个离开的 Observable 可以从每个观察它的 Observer 中删除对自身的反向引用。在这种情况下,这两个班级显然是朋友。

【问题讨论】:

    标签: c++ c++11 observer-pattern observers weak-ptr


    【解决方案1】:

    我坚持的是如何实现退订程序。

    我建议将观察者存储在 std::list 中,因为它的迭代器不会在容器修改时失效。然后在观察者中订阅你将迭代器存储到它,在取消订阅中你使用迭代器来删除元素。 但是,您当然可以按照另一个答案中的建议使用 std::vector 和 std::remove_if 。

    现在关于 *_ptr 的所有内容。在 C++ 中,RAII 是你的朋友,所以使用它。摆脱公共取消订阅方法。相反,观察者必须在其析构函数中取消订阅。这大大简化了事情:不再锁定弱指针:如果观察者已被删除,则它不在列表中。如果您有多线程应用程序,请不要忘记使用互斥锁保护观察者列表。如果您使用这种设计,那么 Observable 将只需要指向 Observers 的普通指针,并且不会要求 Observers 必须如何存储。

    class Observer {
    public:
        void subscribe(std::function<void()> unsubscribe) {
            unsubscribe_ = std::move(unsubscribe);
        }
    
        virtual ~Observer() {
            unsubscribe_();
        }
    private:
        std::function<void()> unsubscribe_;
    };
    
    class Observable {
    public:
        void subscribe(Observer* observer) {
            std::lock_guard<std::mutex> lock(observablesMutex_);
            auto itr = observers_.insert(observers_.end(), observer);
            observer->subscribe([this, itr]{
                std::lock_guard<std::mutex> lock(observablesMutex_);
                observers_.erase(itr);
            });
        }
    
    private:
        std::list<Observer*> observers_;
        std::mutex observablesMutex_;
    };
    

    注意:对于这段代码,Observers 必须始终在 Observable 之前被销毁。


    更新:如果您更习惯于 C++ lambda,您可能会发现在许多情况下,使用 std::function 作为观察者比使用特殊的类层次结构更方便。在这种情况下,您的 API 可以是这样的:

    class Handle {
    public:
        explicit Handle(std::function<void()> onDestroy)
            : onDestroy_(std::move(onDestroy)) {}
    
        Handle(const Handle&) = delete;
    
        Handle(Handle&&) = default;
    
        virtual ~Handle() {
            onDestroy_();
        }
    private:
        std::function<void()> onDestroy_;
    };
    
    class Observable {
    public:
        Handle subscribe(std::function<void()> observer) {
            std::lock_guard<std::mutex> lock(observablesMutex_);
            auto itr = observers_.insert(observers_.end(), observer);
            return {[this, itr]{
                std::lock_guard<std::mutex> lock(observablesMutex_);
                observers_.erase(itr);
            }};
        }
    
    private:
        std::list<std::function<void()>> observers_;
        std::mutex observablesMutex_;
    };
    

    【讨论】:

      【解决方案2】:

      您可以像这样使用std::remove_ifstd::erase

      void Observable::Unsubscribe( std::shared_ptr<Observer> observer )
      {
          std::erase(
              std::remove_if(
                  this->observers.begin(),
                  this->observers.end(),
                  [&](const std::weak_ptr<Observer>& wptr)
                  {
                      return wptr.expired() || wptr.lock() == observer;
                  }
              ),
              this->observers.end()
          );
      }
      

      您确实应该将observer 传递为const std::shared_ptr&lt;Observer&gt;&amp;

      【讨论】:

      • 请注意,std::remove_if 确实从容器中删除元素。您必须同时使用container.erase。搜索erase-remove idiom。
      • ... 虽然在这种情况下,std::remove_if 将(可能)效率低下; std::find_if.erase 会表现得更好(或至少在语义上正确)。
      • 添加了 wptr.expired() 检查安全性并移除死掉的观察者。
      猜你喜欢
      • 2013-01-01
      • 2011-12-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-28
      • 1970-01-01
      • 2015-02-12
      相关资源
      最近更新 更多