C++ 使用弱ptr的观察者模式

C++ 使用弱ptr的观察者模式,c++,c++11,std,smart-pointers,C++,C++11,Std,Smart Pointers,我正试图从中编写一个安全的主题类。我想知道使用weak_ptr是否是存储IObserver实例的最佳方式,以便: 在一个IObserver实例被释放后,不可能使用它 Subject类没有保留应该是免费的IObserver引用() Subject类必须是线程安全的 不幸的是,我们的编码标准规定我们不允许使用boost。我想我前世是个坏人。幸运的是,我被允许使用C++11(Visual Studio 2012附带的) 下面是一个示例Observer类 //支持notify()方法的观察者接口 类

我正试图从中编写一个安全的
主题
类。我想知道使用
weak_ptr
是否是存储
IObserver
实例的最佳方式,以便:

  • 在一个
    IObserver
    实例被释放后,不可能使用它
  • Subject
    类没有保留应该是免费的
    IObserver
    引用()
  • Subject
    类必须是线程安全的
不幸的是,我们的编码标准规定我们不允许使用boost。我想我前世是个坏人。幸运的是,我被允许使用C++11(Visual Studio 2012附带的)

下面是一个示例
Observer

//支持notify()方法的观察者接口
类IObserver
{
公众:
虚拟void notify()常量=0;
虚拟~IObserver(){}
};
//打印消息的具体观察者实现
类观察者:公共IObserver
{
公众:
观察者(const std::string和message):m_message(message){}
void notify()常量{
printf(“%s\r\n”,m_message.c_str());
}
私人:
std::字符串m_消息;
};
这是
主题
课程

//注册观察者并根据需要通知他们的主题。
班级科目
{
公众:
//使用shared_ptr确保观察者现在有效
无效注册表观察服务器(const std::shared_ptr&o)
{
std::锁紧保护装置(m_observer SMUTEX);
m_观察员。推回(o);
}
void unregisterObserver(const std::shared_ptr&o)
{
std::锁紧保护装置(m_observer SMUTEX);
//从m_ObserverSmotex中删除观察者的代码
}
//这是一个在自己的线程中运行的方法,用于通知观察者某些事件
void doNotify()
{
std::锁紧保护装置(m_observer SMUTEX);
//将事件通知任何有效的观察者。
std::for_each(m_observators.cbegin(),m_observators.cend(),
[](常数标准::弱ptr&o)
{
自动观察者=o.锁定();
国际单项体育联合会(观察员){
观察者->通知();
} 
} );
//移除所有已死亡的观察员。这些观察员已过期()。
m_observators.erase(std::remove_if(m_observators.begin(),m_observators.end(),
[](常数标准::弱ptr&o)
{
返回o.过期();
}),m_observators.end();
}
私人:
std::向量m_观测器;
std::互斥m_observer smutex;
};
下面是一些练习
主题的代码

int main(int argc,wchar\u t*argv[]
{
学科;
auto observerHello=std::使_共享(“Hello world”);
subject.registerObserver(observerHello);
{
//创建一个作用域以显示注销。
auto observerBye=std::使_共享(“再见”);
subject.registerObserver(observer-bye);
subject.doNotify();
}
printf(“%s\r\n”,“观察员再见现在被销毁”);
subject.doNotify();
返回0;
}
我使用
弱\u ptr
是否线程安全?从这里我想是的


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

我会对你的
通知有点怀疑--
--假设你激发的某个观察者中有什么东西最终添加或删除了观察者?--坏事发生(包括撞车)。或者阻止另一个线程的操作,谁阻止尝试添加观察者?--坏事情发生了(死锁!)

这是一个棘手的问题。基本上,这是一个重入问题

当你持有一把锁时,永远不要离开对代码的控制。在调用回调时持有锁是不允许的

因此,至少:

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

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

这就留下了一些问题没有解决。例如,删除一个观察者并不保证将来不会调用它!这只是意味着最终它不会被调用

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

一种可能有效的方法是包含添加/删除/通知/killthread事件的任务队列(使killthread成为队列中的一项任务可以大大减少关闭的麻烦)。现在,所有同步都在队列上。如果您无法编写非阻塞无锁队列,通知代码可以简单地锁定、移动队列、解锁,然后继续执行它。或者您可以编写一个队列,使
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();
}
std::向量目标;
{
std::锁紧保护装置(m_observer SMUTEX);
m_observators.erase(std::remove_if(m_observators.begin(),m_observators.end(),
[&目标](常数标准::弱ptr&o)
{
std::shared_ptr ptr=o.lock();
如果(ptr){
目标。推回(ptr);
返回false;
}否则{
返回true;
}
}),m_observators.end();
}
用于(自动和目标:目标){
目标->通知();
}

2012年是否有基于范围的for循环?为什么你们每个人都要用?(与问题无关,呵呵)这只是一种习惯,没有其他原因,只是我已经习惯了。这真的很有帮助,谢谢:)现在我将继续复制和广播,因为在移除一个观察者后接收事件并不是什么大问题。不过,我们必须仔细阅读非阻塞无锁队列。