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