刘未鹏|C++的罗浮宫

Knowledge sharing is the best reuse

刘未鹏ID:pongba
887889次访问,排名36好友14人,关注者167
兴趣:人工智能、机器学习、知识发现,认知科学。
pongba的文章
原创 101 篇
翻译 8 篇
转载 0 篇
评论 1733 篇
刘未鹏的公告
除非特别声明,本站采用Creative Commons License许可。转载请保留作者、出处。非商业。

FeedSkyFeedBurner
或者用 鲜果 GR 抓虾 订阅。

CSDN Blog暂时不支持RSS全文输出,对此感到不便的朋友可以使用强大的greasemonkey脚本:GReader Preview Enhanced(链接),该脚本支持在GReader里面直接打开全文页面。

P.S. 我经常出没于TopLanguage讨论组

《C++的罗浮宫》5年选集(下载)

——知识分享是最大的复用


讨论问题请到TopLanguage小组

TopLanguage


gtalk/msn(邮件请发送到gmail邮箱)

pongba@gmail.com
pp_liu@msn.com

豆瓣 饭否 美味书签

搜索C++的罗浮宫上的内容(不要回车,点击Go)

twitters

books I've translated




这个Blog上都写了哪些东东

最近评论
Microsoft_China_Vip:


推荐给Web程序员,

学习网络赚钱 [ www.888RMB.com.cn ] 为中国赚钱网站,

提供赚钱教程、赚钱经验、赚钱 秘籍、赚钱项目、赚钱产品, 为想赚钱的人群提供一个赚钱的网络平台




Googol:爬过来又看了一遍,顺手把提到的基本书加到豆瓣的想读里。

ps 我不认为“存在即合理”是同义反复。当然这句话本身是高度概括的,甚至是抽象空洞的,因为没有表述请是什么情况下的存在和什么情况下的合理,每个人都可以把自己的理解往里面套。但存在和合理肯定是两个不同的概念,存在是一个物理上的属性,而合理则是逻辑上的。
zwp:这只是一个感想。
nirvash:一心称念弥陀圣号!
fallening:OOps, the discussions seems much more inviting than the article.
文章分类
收藏
相册
其它图片
文章中的图片
我的大头贴
C++
Andrei Alexandrescu
Andrew Lumsdaine
Bjarne Stroustrup
boost
C++ Standard Commitee
Doug Gregor
Hans J. Boehm
Jaakko Jarvi
Jeremy G. Siek
Matthew Wilson
newsgroups
boost.Developer
boost.User
comp.lang.c++.moderated
comp.std.c++
TopLanguage
Open Source
codeplex
Google AJAX Search API
Google Code Prettify
Google Web Toolkit
MS shared source initiative
notepad++
STLSoft
不认识的朋友们
fatalerror99
Glacier
realazy
SpiritEpic
TK
Yelz
YongSun
余晟|乱象&乱想
刘慈欣
吴欣安(atppp)
姬十三
张志强|阅微堂
许式伟
阮一峰
高远
鲍志云
其它
科学松鼠会
科学美国人
科幻世界
认识的朋友们
alai
chenyufei
dd
duguguiyu|Venus神庙
Googol
Joyfire
littlestone
lxwde
Matrix67
realfun
soloist
Tinyfool
windstorm
云风
冯大辉(Fenng)
刘新宇
刘江@图灵
史晓明
周星星
周筠@博文视点
孟岩
张振
徐宥|4G Spaces&Web 2.3
方舟@博文视点
李笑来|Pure Pleasure
杨文博
王信文|地球没有好朋友
荣耀
莫华枫
袁泳(g9)|负暄琐话
谢东升
陈冀康@华章
存档
订阅我的博客
XML聚合  FeedSky
订阅到鲜果
订阅到Google
订阅到抓虾
订阅到BlogLines
订阅到Yahoo
订阅到GouGou
订阅到飞鸽
订阅到Rojo
订阅到newsgator
订阅到netvibes

原创 boost源码剖析之:多重回调机制signal(上)收藏

新一篇: boost源码剖析之:多重回调机制signal(下) | 旧一篇: boost源码剖析之:泛型函数指针类boost::function(rev#3)

boost源码剖析之:多重回调机制signal()

 

刘未鹏

C++的罗浮宫(http://blog.csdn.net/pongba)

 

boost库固然是技术的宝库,却更是思想的宝库。大多数程序员都知道如何应用commandobserver等模式,却不知该如何写一个支持该模式的类。正如隔靴搔痒,无法深入。DDJ上曾有一篇文章用C++实现类似C#event机制,不过是个雏形,比之boost.Signal却又差之甚远矣。

 

上篇:架构篇

引入

所谓事件机制,简而言之,就是用户将自己的一个或多个回调函数挂钩到某个事件上,一旦事件被触发,所有挂钩的函数都被调用。

 

毫无疑问,事件机制是个十分有用且常用的机制,不然C#也不会将它在语言层面实现了。

 

但是C++语言并无此种机制。

 

幸运的是boost库的开发者们替我们做好了这件事(事实上,他们做的还要更多些)。他们的类称作signal,即信号的意思,当信号发出的时候,所有注册过的函数都将受到调用。这与事件本质上完全一样。

 

简单情况下,你只需要这样写:

 

double square(double d){return pi*r*r;} //面积

double circle(double d){return 2*pi*r;} //周长

//double(double)是一个函数类型,意即:接受一个double型参数,返回double

signal<double(double)[1]> sig;

sig.connect(&square); //sig注册square

sig.connect(&circle);//注册circle

//触发该信号,sig会自动调用square(3.14)circle(3.14),并返回最后一个函数,circle()的返回值

double c=sig(3.14);  //assert(c==circle(3.14))

 

signal能够维护一系列的回调函数,并且,signal还允许用户指定函数的调用顺序,signal还允许用户定制其返回策略,默认情况下返回(与它挂钩的)最后一个函数的返回值,当然你可以指定你自己的返回策略”(比如:返回其中的最大值),其中手法,甚为精巧。另外,如果注册的是函数对象(仿函数)而非普通函数,则signal还提供了跟踪能力,即该函数对象一旦析构,则连接自动断开,其实现更是精妙无比。

 

俗语云:熟读唐诗三百首,不会吟诗也会吟。写程序更是如此。如果仔细体会,会发现signal的实现里面隐藏了许许多多有价值的思想和模式。何况boost库是个集泛型技术之大成的库,其源代码本身就是一笔财富,对于深入学习C++泛型技术是极好的教材。所以本文不讲应用,只讲实现,你可以边读边参照boost库的源代码[2]。另外,本文尽量少罗列代码,多分析架构和思想,并且列出的代码为了简洁起见,往往稍作简化[3],略去了一些细节,但是都注明其源文件,自行参照。

 

在继续往下读之前,建议大家先看看boost库的官方文档,了解signal的各种使用情况,这样,在经历下面繁复的分析过程时心中才会始终有一个清晰的脉络。事实上,我在阅读代码之前也是从各种例子入手的。

 

架构

Signal的内部架构,如果给出它的总体轮廓,非常清晰明了。见下图:

 

图一

               

 

显然,signal在内部需要一个管理设施来管理用户所注册的函数(这就是图中的slot manager),从根本上来说,boost::signal中的这个slot“管理器就是multimap(如果你不熟悉multimap,可以参考一些STL方面的书籍(如《C++ STL》《泛型编程与STL》)或干脆查询MSDN。这里我只简单的说一下——multimap将键(key)映射(map)到键值(键和键值的类型可以是任意),就像字典将字母映射到页码一样。)它负责保存所谓的slot每一个slot其实本质上是一个boost::function[4]函数对象该函数对象封装了用户注册给signal回调的函数(或仿函数)。当然,slot是经过某种规则排序的。这正是signal能够控制函数调用顺序的原因。

 

当你触发signal时,其内部迭代遍历管理器”——multimap,找出其中保存的所有函数或函数对象并逐一调用它们。

 

听起来很简单,是不是?但是我其实略去了若干细节,譬如,如何让用户控制某个特定的连接?如何控制函数的调用顺序?如何实现可定制的返回策略?等等。

 

看来设计一个“industry-strength”signal并非一件易事。事实上,非常不易。然而,虽然我们做不到,却可以看看大师们的手笔。

 

我们从signal的最底层布局开始,signal的底层布局十分简单,由一个基类signal_base_impl来实现。下面就是该基类的代码:

 

摘自boost/signals/detail/signal_base.hpp

class signal_base_impl {

public:

typedef function2<bool, any, any> compare_type;

private:

typedef std::multimap<any, connection_slot_pair, compare_type> slot_container_type; //multimap作为slot管理器的类型

 

    //遍历slot容器的迭代器类型

typedef slot_container_type::iterator slot_iterator;

     //slot容器内部元素的类型,事实上,那其实就是std::pair<any,connection_slot_pair>

    typedef slot_container_type::value_type stored_slot_type;

 

     //这就是slot管理器,唯一的数据成员——一个multimap,负责保存所有的slot

    mutable slot_container_type slots_;

...

};

 

可以看出slot管理器的类型是个multimap,其键(key)类型却是any[5],这是个泛型的指针,可以指向任何对象,为什么不是整型或其它类型,后面会为你解释。

以上是主要部分,你可能会觉得奇怪,为什么保存在slot管理器内部的元素类型是个怪异的connection_slot_pair而不是boost::function,前面不是说过,slot本质上就是boost::function对象么?要寻求答案,最好的办法就是看看这个类型定义的代码,源代码会交代一切。下面就是connection_slot_pair的定义: