c++ - 使用weak_ptr的观察者模式

标签 c++ c++11 std smart-pointers

我正在尝试从 observer pattern 编写一个安全的 Subject 类.我想知道使用 weak_ptr 是否是存储 IObserver 实例的最佳方式:

  • IObserver 实例在释放后无法使用。
  • Subject 类不保留应该释放的 IObserver 引用 (lapsed listener problem)。
  • Subject 类必须是线程安全的。

很遗憾,我们的编码标准规定我们不允许使用 boost。我想我前世是个坏人。幸运的是,我可以使用 C++11(Visual Studio 2012 附带的)。

这是一个示例 Observer 类。

// Observer interface that supports notify() method
class IObserver
{
public:
    virtual void notify() const = 0;
    virtual ~IObserver() {}
};

// Concrete observer implementation that prints a message
class Observer : public IObserver
{
public:
    Observer( const std::string& message) : m_message( message ){}

    void notify() const {
        printf( "%s\r\n", m_message.c_str() );
    }

private:
    std::string m_message;
};

这里是 Subject 类。

// Subject which registers observers and notifies them as needed.
class Subject
{
public:
    // Use shared_ptr to guarantee the observer is valid right now
    void registerObserver( const std::shared_ptr<IObserver>& o )
    {
        std::lock_guard<std::mutex> guard( m_observersMutex );
        m_observers.push_back( o );
    }

    void unregisterObserver( const std::shared_ptr<IObserver>& o )
    {
        std::lock_guard<std::mutex> guard( m_observersMutex );
        // Code to remove the observer from m_observersMutex
    }

    // This is a method that is run in its own thread that notifies observers of some event
    void doNotify()
    {
        std::lock_guard<std::mutex> guard( m_observersMutex );
        // Notify any valid observers of events.
        std::for_each( m_observers.cbegin(), m_observers.cend(), 
            []( const std::weak_ptr<IObserver>& o )
        {
            auto observer = o.lock();
            if ( observer ) {
                observer->notify();
            } 
        } );

        // Remove any dead observers.  These are ones which have expired().
        m_observers.erase( std::remove_if( m_observers.begin(), m_observers.end(), 
            []( const std::weak_ptr<IObserver>& o )
        {
            return o.expired();
        } ), m_observers.end() );

    }


private:
    std::vector<std::weak_ptr<IObserver>> m_observers;
    std::mutex m_observersMutex;
};

这是一些练习Subject的代码:

int main(int argc, wchar_t* argv[])
{

    Subject subject;
    auto observerHello = std::make_shared<Observer>( "Hello world" );
    subject.registerObserver( observerHello );
    {
        // Create a scope to show unregistration.
        auto observerBye = std::make_shared<Observer>( "Good bye" );
        subject.registerObserver( observerBye );

        subject.doNotify();
    }
    printf( "%s\r\n", "Observer good bye is now be destructed" );
    subject.doNotify();
    return 0;
}

我对 weak_ptr 的使用是线程安全的吗?从这里 https://stackoverflow.com/a/2160422/1517648认为是的。

这是解决失效听众问题的合法方法吗?

最佳答案

我对你的 doNotify 有点怀疑——假设你解雇的观察者中的某些东西最终会添加或删除观察者? -- 坏事发生(包括崩溃)。或者阻止另一个线程的操作,谁阻止尝试添加观察者? -- 坏事发生(死锁!)

这很难解决。基本上是重入的问题。

当您持有锁时,永远不要离开对代码的控制权。在调用回调时持有锁是不行的。

所以,至少:

锁定,然后复制您的列表,然后解锁。在执行此拷贝时,您还可以删除过期的观察者(从原始列表和拷贝列表中)。

然后从复制的列表中触发观察者。

这留下了一些 Unresolved 问题。比如移除一个观察者并不能保证它以后不会被调用!这只是意味着最终它不会被调用。

这有多重要取决于你如何使用倾听。

一种可行的方法是包含添加/删除/通知/killthread 事件的任务队列(使 killthread 成为队列中的任务可以大大减少关闭的烦人)。现在所有同步都在队列中。如果您无法编写非阻塞无锁队列,则通知代码可以简单地锁定、std::move 队列、解锁,然后继续执行它。或者你可以写一个队列,让 pop 阻塞直到有东西要读,而 push 不会阻塞。

快速而肮脏的“复制和广播”可能如下所示:

std::vector<std::shared_ptr<IObserver>> targets;
{
  std::lock_guard<std::mutex> guard( m_observersMutex );
  m_observers.erase( std::remove_if( m_observers.begin(), m_observers.end(), 
        [&targets]( const std::weak_ptr<IObserver>& o )
    {
      std::shared_ptr<IObserver> ptr = o.lock();
      if (ptr) {
        targets.push_back(ptr);
        return false;
      } else {
        return true;
      }
    } ), m_observers.end() );
}

for( auto& target:targets ) {
  target->notify();
}

关于c++ - 使用weak_ptr的观察者模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14381588/

相关文章:

android - 如何在 Android Studio 中调试 C++ 代码?

c++ - (错误 C2678)错误 : No operator "<<" matches these operands

c++ - 如何将哈希值传递到无序映射以减少持有锁的时间?

python - pybind11:将 c++ 类(具有现有的 python 绑定(bind))返回给 python

c++ - 自定义异常中的损坏消息

c++ - 如何在结构上使用 std::unique_ptr?

安卓NDK : C++ runtime restrictions when using prebuilt shared libraries

c++ - C++多线程并发向单个MySQL表插入数据

c++ - 您如何理解错误 : cannot convert from 'int []' to 'int []'

c# - C# COM DLL 中的 ManagementObject 泄漏