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

系统 1715 0

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

刘未鹏

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

本文的上篇 中,我们大刀阔斧的剖析了 signal 的架构。不过还有很多精微之处没有提到,特别是一个遗留问题还没有解决:如果用户注册的是函数对象(仿函数), signal 又当如何处理呢?

下篇:高级篇

概述

在本文的上篇中,我们已经分析了 signal 的总体架构。至于本篇,我们则主要集中于将 函数对象 (即仿函数)连接到 signal 的来龙去脉。 signal 库的作者在这个方面下了很多功夫,甚至可以说,并不比构建整个 signal 架构的功夫下得少。

之所以为架构,其中必然隐藏着一些或重要或精妙的思想。

学过 STL 的人都知道,函数对象 [1] (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 都是 控制性 [2] 的,一旦析构则自动断开连接。所以, 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:/DOCUME~1/pongba/LOCALS~1/Temp/msohtml1/01/clip_image001.gif"></imagedata></shape>

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

现在的问题是,每当该函数对象连接到一个 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 [3] (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 [4] “this” 参数绑定为 s1 ,从而生成一个 “void()” 型的仿函数,每次调用该仿函数就相当于调用 s1.test() ,然而,这个仿函数本身并非可跟踪的,不过,很显然,这里的 s1 对象一旦析构,则该仿函数就失去了意义,从而应该让连接断开。所以,我们应该使 S1 类成为可跟踪的(见 struct S1 的代码)。

然而,这又能说明什么呢?仍然只有一个 trackable 子对象!但是,答案已经很明显了:既然 boost::bind 可以绑定一个参数,难道不能绑定两个参数?对于一个延迟调用的函数对象 [5] ,一旦其某个按引用语义传递的参数析构了,该函数对象也就相应失效了。所以,对于这种函数对象,其按引用传递的参数都应该是可跟踪的。在上例中, s1 就是一个按引用传递的参数 [6] ,所以是可跟踪的。所以,如果有多个这种参数绑定到一个仿函数,就会有多个 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();

con->signal = static_cast < void *>( this );

con->signal_data = 0;

con->signal_disconnect = &bound_object_destructed;

watch_bound_objects.reset(con);

...

}

这段代码先 new 了一个连接,并将其三个成员设置妥当。由于该连接纯粹仅作 监视 该函数对象是否析构之用,并非真的 连接 slot ,所以 signal_data 成员只需闲置为 0 ,而 signal_disconnect 所指的函数 &bound_object_destructed 也只不过是个什么事也不做的空函数。关键是最后一行代码: watch_bound_objects 乃是 slot 类的成员,类型是 connection ,这行代码使其指向上面新建的 con 连接对象。注意,在后面省略掉的部分代码中, 该连接的副本也被保存到待连接的函数对象的各个 trackable 子对象中 (前面已经提到(参见图四),这系保存在一个 list 中),这才真正使得 监视 成为可能!因为这样做了之后,一旦代连接的函数对象析构了,将会导致 con 连接为 断开 状态。从而在 sig.connect(slot) 时可以通过查询 slot 中的 watch_bound_objects 副本的连接状态得知该 slot 是否有效,如果无效,则返回一个空的连接。这里, connection 巧妙的充当了一个 监视器 的作用。

说到这里,你应该也就明白了为什么 basic_connection signal signal_data 成员的类型为 void* 而不是 signal_base_impl* slot_iterator* —— 是的,因为函数对象不但连接到 signal ,还 连接 slot 。将这两个成员类型设置为 void* 可以复用该类以使其充当 监视器 的角色。 signal 库的作者真可谓惜墨如金。

回到正题,我们接着考察如何将封装了函数对象的 slot 连接到 signal 。这里,我建议你先回顾本文的上篇,因为这与将普通函数连接到 signal 有很大一部分相同之处,只不过多做了一些额外的工作。

同样,可想而知的是,这个连接过程仍然是先将 slot 插入到 signal 中的 slot 管理器中去,并将 signal 的地址,插入后指向该 slot 的迭代器的地址,以及负责断开连接的函数地址分别保存到表示本次连接的 basic_connection 对象的三个成员 [7] 中去。这时,故事几乎已经结束了一半 —— 用户已经可以通过该对象来控制相应连接了。但是,注意,只是 用户 !对于函数对象来说,不但用户能够控制连接,函数对象也必须能够 控制 连接,因为它析构时必须能够断开连接,所以,我们还需要将该连接对象的副本保存到函数对象的各个 trackable 子对象中去:

摘自 libs/signals/src/signal_base.cpp

connection

signal_base_impl::

connect_slot(const any& slot,

const any& name,

const std::vector<const trackable*>& bound_objects)

{

... // 创建 basic_connection 对象并设置其成员

// 下面的 for 循环将该连接的副本保存到各个 trackable 子对象中

for (std::vector<const trackable*>::const_iterator i =

bound_objects.begin();

i != bound_objects.end();++i)

{

bound_object binding;

(*i)->signal_connected(slot_connection, binding );

con->bound_objects.push_back(binding);

}

...

}

在上面的代码中, for 循环遍历绑定到该函数对象的各个 trackable 子对象,并将该连接的副本 slot_connection 保存到其中。这样,当某个 trackable 子对象析构时,就会通过保存在其中的副本来断开该连接,从而达到 跟踪 的目的。

但是,这里还有个问题:这里实际的连接只有一个,但却产生了多个副本,分别操纵在各个 trackable 子对象手中,如果用户愿意,用户还可以操纵一个或多个副本。但是,一旦该连接断开 —— 不管是由于某个 trackable 子对象的析构还是用户手动断开 —— 则保存在各个 trackable 子对象中的该连接的副本都应该被删除掉。不然既占空间又没有任何意义,还会导致这样的情况:只要其中有一个 trackable 对象还没有析构,表示该连接的 basic_connection 对象就不会被 delete 掉。特别是当连接由用户断开时,每个未析构的 trackable 对象中都会仍留有一个该连接对象的副本,直到 trackable 对象析构时该副本才会被删除。这就意味着,如果存在一个 长命百岁 trackable 函数对象,并在其生命期中频繁被用户连接到 signal 并频繁断开连接,那么,每次连接都会遗留一个连接副本在其 trackable 基类子对象中,这是个巨大的累赘。

那么,这个问题到底如何解决呢? basic_connection 仍然是问题的核心,既然用户只能通过 connection 对象来控制连接,而 connection 对象实际上完全通过 basic_connection 来操纵连接,那么如何解决这个问题的责任当然落在 basic_connection 身上 —— 既然它知道哪个函数(对象)连接到哪个 signal 并在其 slot 管理器中的位置,那么,为什么不能让它也知道 该连接在各个 trackable 对象中的副本所在何处 呢?

当然可以。答案就在于 basic_connection 的第四个成员 bound_objects ,其定义如下:

std::list<bound_object> bound_objects;

该成员正是用来记录 该连接在各个 trackable 对象中的副本所在何处 的。它的类型是 std::list ,其中每一个 bound_object 型的对象都代表 某一个连接副本所在之处 。有了它,在断开连接时,就可以依次删除各个 trackable 对象中的副本。

那么,这个 bound_objects 又是何时被填充的呢?当然是在连接时,因为只有在连接时才知道有几个 trackable 对象,并有机会将副本保存到它们内部。我们回顾上文的 connect_slot 函数的代码,其中有加底纹的部分刚才没有分析,这正是与此相关的。为了清晰起见,我们将分析以源代码注释的形式写出来:

//bound_object 对象保存的是连接副本在 trackable 对象中的位置

bound_object binding;

// 调用的是 trackable::signal_connected 函数,该函数告诉 trackable 对象它已经连接到了 signal ,并提供连接的副本(第一个参数),该函数会将该副本插入到 trackable 的成员 connected_signals (见篇首 trackable 类的代码)中去。并将插入的位置反馈给 binding 对象(第二个参数,按引用传递),这时候,通过 binding 就能够将该副本从 trackable 对象中删除。

(*i)->signal_connected(slot_connection, binding );

// 将接受反馈后的 binding 对象保存到该连接的 bound_objects 成员中去,以便以后通过它来删除连接的副本

con->bound_objects.push_back(binding);

要想完全搞清楚以上几行代码,我们还得来看看 bound_object 类的结构以及 trackable::signal_connected 到底干了些什么?先来看看 bound_object 的结构:

摘自 boost/signals/connection.hpp

struct bound_object {

void * obj;

void * data;

void (*disconnect)( void *, void *);

}

发现什么特别的没有?是的,它的结构简直就是 basic_connection 的翻版,只不过成员的名字不同了而已。 basic_connection 因为是控制连接的枢纽,所以其三个成员表现的是被连接的 slot signal 中的位置。而 bound_object 表现的是 connection 副本在 trackable 对象中的位置。在介绍 bound_object 的三个成员之前,我们先来考察 trackable::signal_connected 函数,因为这个函数同时也揭示了这三个成员的含义:

摘自 libs/signals/src/trackable.cpp

void trackable::signal_connected(connection c,

bound_object& binding )

{

// connection 副本插入到 trackable 对象中的 connected_signals 中去, connected_signals 是个 std::list<connection> 型的容器,负责跟踪该对象连接到了哪些 signal (见篇首的详述)。

connection_iterator pos =

connected_signals.insert(connected_signals.end(), c);

// 将该 trackable 对象中保存的 connection 副本设置为 控制性 的,从而该副本析构时才会自动断开连接。

pos->set_controlling();

//obj 指针指向 trackable 对象,注意这里将 trackable* 转型为 void* 以利于保存。

binding.obj = const_cast < void *>( reinterpret_cast < const void *>( this ));

//data 指向 connection 副本在 connected_signals 容器中的位置,注意这里的转型

binding.data = reinterpret_cast < void *>( new connection_iterator(pos));

// 通过这个函数指针,可以将这个 connection 副本删除: signal_disconnected 函数接受 obj data 为参数,将 connection 副本 erase

binding.disconnect = & signal_disconnected ;

}

分析完了这段代码, bound_object 类的三个成员的含义不言自明。注意,其最后一个成员是个函数指针,指向 trackable::signal_disconnected 函数,这个函数负责将一个 connection 副本从某个 trackable 对象中删除,其参数有二,正是 bound_object 的前两个成员 obj data ,它们合起来指明了一个 connection 副本的位置。

当这些副本在各个 trackable 子对象中都安置妥当后,连接就算完成了。我们再来看看连接具体是如何断开的,对于函数对象,断开它与某个 signal 的连接的过程大致如下:首先,与普通函数一样,将函数对象从 signal slot 管理器中 erase 掉,这个连接就算断开了。其次就是只与函数对象相关的动作了:将保存在绑定到函数对象的各个 trackable 子对象中的 connection 副本清除掉。这就算完成了断开 signal 与函数对象的连接的过程。当然,得看到代码心里才踏实,下面就是:

void connection::disconnect()

{

if ( this ->connected()) {

shared_ptr<detail::basic_connection> local_con = con;

// 先将该函数指针保存下来

void (*signal_disconnect)( void *, void *) =

local_con->signal_disconnect;

// 然后再将该函数指针置为 0 ,表示该连接已断开

local_con->signal_disconnect = 0;

// 断开连接, signal_disconnect 函数指针指向 signal_base_impl::slot_disconnected 函数,该函数在本文的上篇已作了详细介绍

signal_disconnect(local_con->signal, local_con->signal_data);

// 清除保存在各个 trackable 子对象中的 connection 副本

typedef std::list<bound_object>::iterator iterator;

for (iterator i = local_con->bound_objects.begin();

i != local_con->bound_objects.end(); ++i) {

// 通过 bound_object 的第三个成员, disconnect 函数指针来清除该连接的每个副本

i->disconnect(i->obj, i->data);

}

}

}

前面已经说过, bound_object 的第三个成员 disconnect 指向的函数为 trackable::signal_disconnected ,顾名思义, “signal” 已经 “disconnected” 了,该是清除那些多余的 connection 副本的时候了,所以,上面的最后一行代码 “i->disconnect(...)” 就是调用该函数来做最后的清理工作的:

摘自 libs/signals/src/trackable.cpp

void trackable::signal_disconnected( void * obj, void * data)

{

// 将两个参数转型,还其本来面目

trackable* self = reinterpret_cast <trackable*>(obj);

connection_iterator* signal =

reinterpret_cast <connection_iterator*>(data);

if (!self->dying) {

// connection 副本 erase

self->connected_signals.erase(*signal);

}

delete signal;

}

这就是故事的全部。这个清理工作一完成,函数对象与 signal 就再无瓜葛,从此分道扬镳。回过头来再看看 signal 库对函数对象所做的工作,可以发现,其主要围绕着 trackable 类的成员 connected_signals basic_connection 的成员 bound_objects 而展开。这两个一个负责保存 connection 的副本以作跟踪之用,另一个则负责在断开连接时清除 connection 的各个副本。

分析还属其次,重要的是我们能够从中汲取到一些纳为己用的东西。关于 trackable 思想,不但可以用在 signal 中,在其它需要跟踪对象析构语义的场合也大可用上。这种架构之最妙之处就在于用户只要作一个简单的派生,就获得了完整的对象跟踪能力,一切的一切都在背后严密的完成。

蛇足 & 再谈调用

还记得在本文的上篇分析的 调用 部分吗?库的作者藉由一个所谓的 “slot_call_iterator” 来完成遍历 slot 管理器和调用 slot 的双重任务。 slot_call_iterator slot 管理器本身的 iterator 语义几乎相同,只不过对前者解引用 (dereference ,即 “*iter”) 的背后其实调用了其指向的 slot 函数,并且返回的是 slot 函数的返回值。这种特殊的语义使得 signal 可以将 slot_call_iterator 直接交给用户制定的返回策略(如 max_value<> min_value<> 等),一石二鸟。但是这里面有一个难以察觉的漏洞:一个设计得不好的算法可能会使迭代器在相同的位置上出现冗余的解引用,例如,一个设计的不好的 max_value<> 可能会像这样:

T max = *first++;

for (; first != last; ++first)

max = ( *first > max)? *first : max;

这个算法本身的逻辑并没有什么不妥,只不过注意到其中 *first 出现了两次,这意味着什么?如果按照以前的说法,每一次解引用都意味着一次函数调用的话,那么同一个函数将被调用两次。这可就不合逻辑了。 signal 必须保证每个注册的函数有且仅有一次执行的机会。

解决这个问题的任务落在库的设计者身上,无论如何,一个普通用户写出上面的算法的确是件无可非议的事。一个明显的解决方案是将函数的返回值缓存起来,第二次或第 N 次在同一位置解引用时只是从缓存中取值并返回。 signal 库的设计者正是采用的这种方法,只不过, slot_call_iterator 将缓存的返回值交给一个 shared_ptr 来掌管。这是因为,用户可能会拷贝迭代器,以暂时保存区间中的某个位置信息,在拷贝迭代器时,如果缓存中已经有返回值,即函数已经调用过了,则新的迭代器也因该引用那个缓存。并且,当最后一个引用该缓存的迭代器消失时,就是该缓存被释放之时,这正是 shared_ptr 用武之地。具体的实现代码请你自行参考 boost/signals/detail/slot_call_iterator.hpp

值得注意的是, slot_call_iterator 符合 “single pass” (单向遍历) concept 。对于这种类型的迭代器只能进行两种操作:递增和比较。这就防止了用户写出不规矩的返回策略 —— 例如,二分查找(它要求一个随机迭代器)。如果用户硬要犯规,就会得到一个编译错误。

由此可见,设计一个完备的库不但需要技术,还要无比的细心。

结语

相对于 C++ 精致的泛型技术的应用来说,其背后隐藏的思想更为重要。在 signal 库中,泛型技术的应用其实也不可不谓淋漓尽致,但是语言只是工具,重要的是解决问题的思想。从这篇文章可以看出,作者为了构建一个功能完备,健壮,某些特性可定制的 signal 架构付出了多少努力。虽然某些地方看似简单,如 connection 对象,但是都是经过反复揣摩,时间检验后作出的设计抉择。而对于函数对象,更是以一个 trackable 基类就实现了完备的跟踪能力。以一个函数对象来定制返回策略则是符合 policy-based 设计的精髓。另外还有一些细致入微的设计细节,本篇并没有一一分析,一是为了让文章更紧凑,二是篇幅 —— 只讲主要脉络文章尚已如此,再加上各个细节则更是 了得 了,干脆留给你自行理解,你将 boost 的源代码和本文列出的相应部分比较后或会发现一些不同之处,那些就是我故意省略掉的细节所在了。对于细节有兴趣的不妨自己分析分析。

目录 ( 展开 boost 源码剖析》系列 文章 )



[1] 函数对象即重载了 operator() 操作符的对象,故而可以以与函数调用一致的语法形式来 调用 。又称为 functor ,中文译为 仿函数

[2] 控制性 是指该 connection 析构时会顺便将该连接断开。反之则不然。关于 控制性 非控制性 connection 的详细讨论见本文的上篇。

[3] get_inspectable_slot() 当且仅当 f 是个 reference_wrapper 时,返回 f.get()—— 即其中封装的真实的函数(对象)。其它时候,该函数调用等同于 f 。关于 reference_wrapper 的详细介绍见 boost 的官方文档。

[4] &S1::test 为指向成员函数的指针。其调用形式为 (this_ptr->*mem_fun_ptr)() (this_ref.*mem_fun_ptr)() ,而从一般语义上说,其调用形式为 mem_fun_ptr(this_ref) mem_fun_ptr(this_ptr) 。所以, boost::bind 可以将其“第一个”参数绑定为 s1 对象。

[5] command 模式,其中封装的 command 对象就是一个延迟调用的函数对象,它暂时保存某函数及其调用的各个参数,并在恰当的时候调用该函数。

[6] boost::ref(s1) 生成一个 boost::reference_wrapper<S1>(s1) 对象,其语义与 裸引用 几乎一样,只不过具有拷贝构造,以及赋值语义,这有点像 java 里面的对象引用。具体介绍见 boost 的官方文档。

[7] signal 成员指向连接到的 signal signal_data 成员指向该函数在 signal 中保存的位置(一般为迭代器),而 signal_disconnect 则是个函数指针,负责断开连接,将前两个成员作为参数传给它就可以断开连接。

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


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论