boost源码剖析之:多重回调机制signal(下)
刘未鹏
C++的罗浮宫(http://blog.csdn.net/pongba)
在本文的上篇中,我们大刀阔斧的剖析了signal的架构。不过还有很多精微之处没有提到,特别是一个遗留问题还没有解决:如果用户注册的是函数对象(仿函数),signal又当如何处理呢?
下篇:高级篇
概述
在本文的上篇中,我们已经分析了signal的总体架构。至于本篇,我们则主要集中于将函数对象(即仿函数)连接到signal的来龙去脉。signal库的作者在这个方面下了很多功夫,甚至可以说,并不比构建整个signal架构的功夫下得少。
之所以为架构,其中必然隐藏着一些或重要或精妙的思想。
学过STL的人都知道,函数对象(function object)是STL中的重要概念和基石之一。它使得一个对象可以像函数一样被“调用”,而调用形式又是与函数一致的。这种一致性在泛型编程中乃是非常重要的,它意味着“泛化”,而这正是泛型世界所有一切的基础。而函数对象又由于其携带的信息较之普通函数大为丰富,从而具有更为强大的能力。
所以signal简直是“不得不”支持函数对象。然而函数对象又和普通函数不同:函数对象会析构。问题在于:如果某个函数对象连接到signal,那么,该函数对象析构时,连接是否应该断开呢?这个问题,signal的设计者留给用户来选择:如果用户觉得函数对象一旦析构,相应的连接也应该自动断开,则可以将其函数对象派生自boost::signals::trackable类,意即该对象是“可跟踪”的。反之则不用作此派生。这种跟踪对象析构的能力是很有用的,在某些情况下,用户需要这种语义:例如,一个负责数据库访问及更新的函数对象,而该对象的生命期受某个管理器的管理,现在,将它连接到某个代表用户界面变化的signal,那么,当该对象的生命期结束时,对应的连接显然应该断开——因为该对象的析构意味着对应的数据库不再需要更新了。
signal库支持跟踪函数对象析构的方式很简单,只要将被跟踪的函数对象派生自boost::signals::trackable类即可,不需要任何额外的步骤。解剖这个trackable类所隐藏的秘密正是本文的重点。
架构
很显然,trackable类是整个问题的关键。将函数对象派生自该类,就好比为函数对象安上了一个“跟踪器”。根据C++语言的规则,当某个对象析构时,先析构派生层次最高(most derived)的对象,再逐层往下析构其子对象。这就意味着,函数对象的析构最终将会导致其基类trackable子对象的析构,从而在后者的析构函数中,得到断开连接的机会。那么,哪些连接该断开呢?换句话说,该断开与哪些signal的连接呢?当然是该函数对象连接到的signals。而这些连接则全部保存在一个list里面。下面就是trackable的代码:
class trackable {
typedef std::list<connection> connection_list;
typedef connection_list::iterator connection_iterator;
mutable connection_list connected_signals;
...
}
connected_signals是个list,其中保存的是该函数对象所连接到的signals。只不过是以connection的形式来表示的。这些connection都是“控制性”的,一旦析构则自动断开连接。所以,trackable析构时根本不需要任何额外的动作,只要让该list自行析构就行了。
了解了这一点,就可以画出可跟踪的函数对象的基本结构,如图四:
图四
<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 204.75pt; HEIGHT: 233.25pt" type="#_x0000_t75"><imagedata o:title="boost" src="file:///C:%5CDOCUME~1%5Cpongba%5CLOCALS~1%5CTemp%5Cmsohtml1%5C01%5Cclip_image001.gif"></imagedata></shape>
现在的问题是,每当该函数对象连接到一个signal,都会将相应connection的一个副本插入到其trackable子对象的connected_signals成员(一个list)中去。然而,这个插入究竟发生在何时何地呢?
在本文的上篇中曾经分析过连接的过程。对于函数对象,这个过程仍然是一样。不过,当时略过了一些细节,这些细节正是与函数对象相关的。现在一一道来:
如你所知,在将函数(对象)连接到signal时,函数(对象)会先被封装成一个slot对象,slot类的构造函数如下:
slot(const F& f):slot_function(get_invocable_slot(f,tag_type(f)))
{
//一个visitor,用于访问f中的每个trackable子对象
bound_objects_visitor do_bind(bound_objects);
//如果f为函数对象,则访问f中的每一个trackable子对象
visit_each(do_bind,get_inspectable_slot(f,tag_type(f)));
//创建一个connection,表示f与该slot的连接,这是为了实现“delayed-connect”
create_connection();
}
bound_objects是slot类的成员,其类型为vector<const trackable*>。可想而知,经过第二行代码“visit_each(...)”的调用,该vector中保存的将是指向f中的各个trackable子对象的指针。
“等等!”你敏锐的发现了一个问题:“前面不是说过,如果用户要让他的函数对象成为可跟踪的,则将该函数对象派生自trackable对象吗?那么,也就是说,如果f是个“可跟踪”的函数对象,那么其中的trackable子对象当然只有一个(基类对象)!但为什么这里bound_objects的类型却是一个vector呢?单单一个trackable*不就够了么?”
在分析这个问题之前,我们先来看一段例子代码:
struct S1:boost::signals::trackable
{//该对象是可跟踪的!但并非一个函数对象
void test(){cout<<"test\n";}
};
...
boost::signal<void()> sig;
{ //一个局部作用域
S1 s1;
sig.connect(boost::bind(&S1::test,boost::ref(s1)));
sig(); //输出 “test”
} //结束该作用域,s1在此析构,断开连接
sig(); //无输出
boost::bind()将&S1::test的“this”参数绑定为s1,从而生成一个“void()”型的仿函数,每次调用该仿函数就相当于调用s1.test(),然而,这个仿函数本身并非可跟踪的,不过,很显然,这里的s1对象一旦析构,则该仿函数就失去了意义,从而应该让连接断开。所以,我们应该使S1类成为可跟踪的(见struct S1的代码)。
然而,这又能说明什么呢?仍然只有一个trackable子对象!但是,答案已经很明显了:既然boost::bind可以绑定一个参数,难道不能绑定两个参数?对于一个延迟调用的函数对象,一旦其某个按引用语义传递的参数析构了,该函数对象也就相应失效了。所以,对于这种函数对象,其按引用传递的参数都应该是可跟踪的。在上例中,s1就是一个按引用传递的参数,所以是可跟踪的。所以,如果有多个这种参数绑定到一个仿函数,就会有多个trackable对象,其中任意一个对象的析构都会导致仿函数失效以及连接的断开。
例如,假设C1,C2类都是trackable的。并且函数test的类型为void(C1,C2)。那么boost::bind(&test,boost::ref(c1),boost::ref(c2))就会返回一个void()型的函数对象,其中c1,c2作为test的参数绑定到了该函数对象。这时候,如果c1或c2析构,这个函数对象也就失效了。如果先前该函数对象曾连接到某个signal<void()>型的signal,则连接应该断开。
问题在于,如何获得绑定到某个函数对象的所有trackale子对象呢?
关键在于visit_each函数——我们回到slot的构造函数(见上文列出的源代码),其第二行代码调用了visit_each函数,该函数负责访问f中的各个trackable子对象,并将它们的地址保存在bound_objects这个vector中。
至于visit_each是如何访问f中的各个trackable子对象的,这并非本文的重点,我建议你自行参考源代码。
slot类的构造函数最后调用了create_connection函数,这个函数创建一个连接对象,表示函数对象和该slot的连接。“咦?为什么和slot连接,函数对象不是和signal连接的吗?”没错。但这个看似蛇足的举动其实是为了实现“delayed connect”,例如:
void delayed_connect(Functor* f)
{
//构造一个slot,但暂时不连接
slot_type slot(*f);
//使用f做一些事情,在这个过程中f可能会被析构掉
...
//如果f已经被析构了,则slot变为inactive态,则下面的连接什么事也不做
sig.connect(slot);
}
...
Functor* pf=new Functor();
delayed_connect(pf);
...
这里,如果在slot连接到sig之前,f“不幸”析构了,则连接不会生效,只是返回一个空连接。
为了达到这个目的,slot类的构造函数使用create_connection构造一个连接,这个连接其实没有实际意义,只是用于“监视”函数对象是否析构。如果函数对象析构了,则该连接会变为“断开”态。下面是create_connection的源代码:
摘自libs/signals/src/slot.cpp
void slot_base::create_connection()
{
basic_connection* con = new basic_connection();
font-size: 12p
分享到:
相关推荐
boost::asio::serial下6个工程演示多种串口读取写入方式方法,包含simple,with_timeout,async,callback,qt_integration,stream 等多个工程演示多种方式读取,写入串口,char,string ,buffer[]等多种数据格式。
通过boost::asio::serialport类实现串口通信的例子
详细讲述了boost::thread的用法
STL-Boost:STL-Boost源码剖析
boost源码 boost源码 boost源码 boost源码 boost源码 boost源码
boost库,dns解析模块源码。 将其放倒boost库的相关目录下,在代码中,直接包含头文件即可使用。
使用VS2017编译的boost库最新版1.68的动态库和静态库,多线程参数,经过测试可用
如果你还在为自己写的程序存在各种野指针,内存泄漏,甚至崩溃的问题而苦恼的话,请使用boost;如果你还在为自己写的程序存在很多与Windows依赖的操作导致无法跨平台而困扰的话,请赶紧采用boost吧. 它将为你提供丰富多样...
第6章Boost深入剖析之使用技巧 《Boost深入剖析之使用技巧》第四讲:Boost容器库(中).flv 如果你还在为自己写的程序存在各种野指针,内存泄漏,甚至崩溃的问题而苦恼的话,请使用boost;如果你还在为自己写的程序存在...
本发布版的目录树包含了Boost的几乎所有东西:文档、源程序、头文件、脚本、工具,以及一个Boost用户可能需要的所有东西! 欢迎来到 Boost C++ 库 Boost 提供了免费的、对等审查的、可移植的 C++ 源程序库。 我们...
boost::lexical_cast用法示例,包含数值转字串,字串转数值以及相应的异常处理代码
boost的1.65.1的源码,可以在Linux平台下,编译为二进制,作为第三方库使用。
资源名称:C 编程 boost库资料大全资源目录:【】01date_time【】02smart_ptr【】03pool【】04usefultools【】05lexical_cast【】Boost库概览【】Boost程序库完全开发指南【】cboost【】可移植的C 标准库Boost【】用...
using boost::system::error_code; struct logger { void operator ()(error_code ec, message m) { cout << m << endl; } }; void main () { io_service io; dbus::proxy avahi (io, dbus::...
我自己制作的PDF目录,很详细的。 作为C++的准标准库,在C++的开发中的地位已经变得很重要了,自从发现了这本书后,我就开始迫不及待看起来。但是,从网上下载的许多pdf都是没有目录的,要么就是加密了的,所以我...
C++的最新标准(C++11)已经正式公布,而早在这之前,Boost就已经使用库的形式实现了大部分新功能——而且是完全基于C++98标准实现的,内容涵盖智能指针、文本处理、并发、模板元编程等许多领域,其范围之广内涵之深...
boost::asio完成了通讯模块的编写,界面用MFC简单做了一下。 局域网的测试结果: 传输速度在6-7m/s 并发到500,服务器CPU和网络应用均出现使用99%的情况出现硬件瓶颈,新连接无法建立(测试服务器比较差,CPU:...
需要包含boost的路径为: include path: F:\boost_1_53_0 lib path:F:\boost_1_53_0\stage\lib 需要预定义的宏: _WIN32_WINNT=0x0700 在程序里写的接收ip为192.168.1.206,端口为 9002
BOOST程序库完全开发指南:深入...以及boost源码1.71.0 BOOST程序库完全开发指南:深入C++“准”标准库(第3版) 以及boost源码1.71.0 BOOST程序库完全开发指南:深入C++“准”标准库(第3版) 以及boost源码1.71.0