言归正传,sigslot是一个用标准C++语法实现的信号与槽机制的函数库,类型和线程安全。提到信号与槽机制,恐怕最容易想到的就是大名鼎鼎的Qt所支持的对象之间通信的模式吧。不过这里的信号与槽虽然在概念上等价与Qt所实现的信号与槽,但是采用的仅仅是标准C++语法,不像Qt采用了扩展C++语言的方式(Qt需要使用moc工具对代码预处理之后,才能由标准的C++编译器进行编译)。
众所周知,C++是一门特性众多的语言,其支持多种编程范式。虽然C++在一定程度上支持OOP编程,但是C++这种“静态消息机制”的语言一直没有实现对象级别的delegate机制,而C++之父Bjarne主张的“库扩展胜于语言扩展”的做法使得各种解决方案层出不穷。除了信号与槽机制,C++11正式加入的std:bind/std::function组合也提供了优秀的解决方案。这里所说的信号与槽机制也是一种对象间通信的机制,具体的讨论也可以看看sigslot相关介绍中的内容。
sigslot主页: http://sigslot.sourceforge.net
sigslot文档: http://sigslot.sourceforge.net/sigslot.pdf
sigslot 库的用法文档中已然很明了了,所以在这里就不赘述了。接下来我们看看这个库的实现。源码分析的方法有很多种,具体到库代码的分析的方法,我喜欢的是先研究库的功能,直到能写出一个demo程序为止。研究一个库的前提是你得会用它,熟悉它的接口。读完文档,很容易就写出了下面的测试代码:
#include #include "sigslot.h"using namespace sigslot;class Switch
{
public:signal0<> clicked;
};class Light : public has_slots<>
{
public:void turn_on(){std::cout << "Turn on ~" << std::endl;}};int main(int argc, char *argv[])
{Light lit1;Switch swh;swh.clicked.connect(&lit1, &Light::turn_on);swh.clicked.emit();return 0;
}
使用方法很简单。从这里我们就能看出来,这个库无非就是在信号那一端保存了这个信号所绑定的函数指针,在槽函数这一端保存了其绑定的信号而已。接下来的问题实际上就是采用合理的数据结构来处理问题了。
sigslot库简单到只有一个头文件 sigslot.h ,打开后洋洋洒洒几千行代码,其实仔细看看绝大多数代码都是为了适应参数数量不同的成员函数指针的定义,为其扩展的模版代码。从定义上看,这个库支持 0~8 个参数的成员函数绑定。在纸上画一下类的继承关系,很容易就得到了如下的函数继承图( IDE 有相关的工具也可以拿来用 ~ ) :
从这个图上看,其实代码关系已经很清晰了。 实现了槽函数的类需要继承 has_slots 类。而 has_slots类 拥有一个 std::set<_signal_base<mt_policy>*> 类型的容器(所有的 mt_policy 其实是库定义的三种锁策略而已[单线程无锁、多线程共享全局锁、多线程局部锁])。所有的 _signal_base[0-8] 的类持有各自的 std::list<_connection_base[0-8]<mt_policy > > 的list容器,而 _connection_base[0-8] 则分别封装了0~8个参数的成员函数的指针。
这里的重复代码是很多的,作为分析的话完全可以每中类代码只留下一个,这样所有的代码就精简到只有6个类了(反正别的也只是为了适应参数个数写的模版罢了,代码除了参数个数外都是一样的)。
至于前面说到的锁,其实也只是因为C++ STL库中的容器本身不是线程安全的,需要在外部加锁。锁的实现很平常,另外用C++ RAII手法封装的lock_block类也是常见的用法。唯一需要注意的是,这个库在使用了信号与槽的用户类发生了拷贝构造时,其信号与槽的绑定关系也会被拷贝,所以代码中的类都自行编写了相关的拷贝构造函数。这里稍微解释下,如果A类的a对象的x信号和B类的b1对象的y函数绑定,然后用b1初始化构造b2(即 B b2(b1))。这时候,A类的a对象的x信号也会绑定到b2对象的y函数。这个特性我感觉有点莫名其妙,而且使得代码复杂了不少(我觉得没必要这么设计,用户需要这个特性的话,自己再调用一次绑定函数就是了)。
知晓了基本的原理之后,看代码就很容易了。比如在拥有信号和拥有槽函数的对象析构时,会自动的取消掉之前的绑定,代码很清晰易读的。下面是我自己根据sigslot的原理,简化出来的代码,大家可以先看看然后去读sigslot的源码会简单很多。