设为首页 加入收藏

TOP

1.14 Observer 之谬
2013-10-07 16:03:14 来源: 作者: 【 】 浏览:65
Tags:1.14 Observer 之谬

1.14 Observer 之谬

本章§1.8 把shared_ptr/weak_ptr 应用到Observer 模式中,部分解决了其线程安全问题。我用Observer 举例,因为这是一个广为人知的设计模式,但是它有本质的问题。

Observer 模式的本质问题在于其面向对象的设计。换句话说,我认为正是面向对象(OO)本身造成了Observer 的缺点。Observer 是基类,这带来了非常强的耦合,强度仅次于友元(friend)。这种耦合不仅限制了成员函数的名字、参数、返回值,还限制了成员函数所属的类型(必须是Observer 的派生类)。

Observer class 是基类,这意味着如果Foo 想要观察两个类型的事件(比如时钟和温度),需要使用多继承。这还不是最糟糕的,如果要重复观察同一类型的事件(比如1 秒一次的心跳和30 秒一次的自检),就要用到一些伎俩来work around,因为不能从一个Base class 继承两次。

现在的语言一般可以绕过Observer 模式的限制,比如Java 可以用匿名内部类,Java 8 用Closure,C# 用delegate,C++(www.cppentry.com) 用boost::function/ boost::bind 2。

C++(www.cppentry.com) 里为了替换Observer,可以用Signal/Slots,我指的不是QT 那种靠语言扩展的实现,而是完全靠标准库实现的thread safe、race condition free、threadcontention free 的Signal/Slots,并且不强制要求shared_ptr 来管理对象,也就是说完全解决了§1.8 列出的Observer 遗留问题。这会用到§2.8 介绍的“借shared_ptr实现copy-on-write”技术。

在C++(www.cppentry.com)11 中,借助variadic template,实现最简单(trivial)的一对多回调可谓不费吹灰之力,代码如下。

  1. recipes/thread/SignalSlotTrivial.h  
  2. template<typename Signature> 
  3. class SignalTrivial;  
  4. // NOT thread safe !!!  
  5. template <typename RET, typename... ARGS> 
  6. class SignalTrivial<RET(ARGS...)> 
  7. {  
  8. public:  
  9. typedef std::function<void (ARGS...)> Functor;  
  10. void connect(Functor&& func)  
  11. {  
  12. functors_.push_back(std::forward<Functor>(func));  
  13. }  
  14. void call(ARGS&&... args)  
  15. {  
  16. for (const Functor& f: functors_)  
  17. {  
  18. f(args...);  
  19. }  
  20. }  
  21. private:  
  22. std::vector<Functor> functors_;  
  23. };  
  24. recipes/thread/SignalSlotTrivial.h 

我们不难把以上基本实现扩展为线程安全的Signal/Slots,并且在Slot 析构时自动unregister。有兴趣的读者可仔细阅读完整实现的代码(recipes/thread/SignalSlot.h)。

结语

《C++(www.cppentry.com) 沉思录》(Ruminations on C++(www.cppentry.com) 中文版)的附录是王曦和孟岩对作者夫妇二人的采访,在被问到“请给我们三个你们认为最重要的建议”时,Koenig 和Moo 的第一个建议是“避免使用指针”。我2003 年读到这段时,理解不深,觉得固然使用指针容易造成内存方面的问题,但是完全不用也是做不到的,毕竟C++(www.cppentry.com) 的多态要通过指针或引用来起效。6 年之后重新拾起来,发现大师的观点何其深刻,不免掩卷长叹。

这本书详细地介绍了handle/body idiom,这是编写大型C++(www.cppentry.com) 程序的必备技术,也是实现物理隔离的“法宝”,值得细读。

目前来看,用shared_ptr 来管理资源在国内C++(www.cppentry.com) 界似乎并不是一种主流做法,很多人排斥智能指针,视其为“洪水猛兽”(这或许受了auto_ptr 的垃圾设计的影响)。据我所知,很多C++(www.cppentry.com) 项目还是手动管理内存和资源,因此我觉得有必要把我认为好的做法分享出来,让更多的人尝试并采纳。我觉得shared_ptr 对于编写线程安全的C++(www.cppentry.com) 程序是至关重要的,不然就得“土法炼钢”,自己“重新发明轮子”。这让我想起了2001 年前后STL 刚刚传入国内,大家也是很犹豫,觉得它性能不高,使用不便,还不如自己造的容器类。10 年过去了,现在STL 已经是主流,大家也适应了迭代器、容器、算法、适配器、仿函数这些“新”名词、“新”技术,开始在项目中普遍使用(至少用vector 代替数组嘛)。我希望,几年之后人们回头看本章内容,觉得“怎么讲的都是常识”,那我的写作目的也就达到了。

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇1.13 心得与小结 下一篇6.3.1 代码结构(1)

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容:

·MySQL 安装及连接-腾 (2025-12-25 06:20:28)
·MySQL的下载、安装、 (2025-12-25 06:20:26)
·MySQL 中文网:探索 (2025-12-25 06:20:23)
·Shell脚本:Linux Sh (2025-12-25 05:50:11)
·VMware虚拟机安装Lin (2025-12-25 05:50:08)