我是
靠谱客的博主
腼腆发卡,最近开发中收集的这篇文章主要介绍
sigslot源代码分析解释一下这个UML图代码细节,觉得挺不错的,现在分享给大家,希望可以做个参考。
概述
原文链接:http://my.oschina.net/tianxialangui/blog/67005
对signal/slot机制非常感兴趣,所以网上找了几份实现,不过能力不足,大部分实现感觉有点难度,后来看到sigslot库,这个非常简单,核心代码其实就500来行代码。没有心理压力,所以写一份源代码分析,表示我也看懂了一份工业级别的源代码:
这是我自己画的一份sigslot的UML图片,对sigslot的源代码做了如下简化:
- 消除了signal1...signaln之类的重复代码,这样子就消除了connect1...connectn这些“多余”的代码了。这样简化了很多。
- 去除了多线程管理代码,这个在分析源代码的时候没有什么作用。
第二,这个UML我加上了一些自己的东西:
例如图中的has_slots和signal_base,根据UML的规则,这说明has_slots和_signal_base是一对多的关系,即一个has_slots对象可以拥有多个_signal_base对象,我添加的内容是,has_slots和_signal_base的连线,一端指向了has_slots的成员变量m_senders,这说明一个has_slots是通过m_senders这个私有变量来“拥有”多个_signal_base对象的,至于m_senders是std::set类型的,这并不是我们所关心的,所以UML图中并没有表现出来。
解释一下这个UML图
如果理解了sigslot的源代码,看看这个UML图,代码思路是很清晰的。现在还是让我来说明一下这个UML图吧。
为什么需要has_slots类?为了什么有slot的类要继承自has_slots类呢?
因为has_slots实现了对signal对象的管理。我们来看一下如下的代码:
14 | s1.connect (&a_boj, &A::f); |
15 | s2.connect (&a_obj, &A::f); |
16 | s3.connect (&a_obj, &A::h); |
当类A的对象a_obj离开自己的作用域时,a_obj被析构。上面的代码中,类A没有继承自
has_slots,所以类A中的析构函数其实什么也没有做。也就是说,s还是连接中对象a_obj
和&A::f的连接。所以在a_obj的作用域外面调用s.emit()会导致一些问题。
由于signal和slot机制中,signal完全失去对拥有slot对象的控制权,所以,断开连接的操作,应该在类A的析构函数中完成
因为类A对象的 slot 可能连接到了许多不同的signal对象,类A的析构函数中,要自动断开所有这些signal的
连接。所以对象a中需要存储所有signal对象。代码中是采用一个set保存的:
这正是UML图中,has_slots和signal_base的关系为一对多的原因。
关于_signal_base的继承关系
由于C++对可变模板形参没有提供支持,所以库的作者使用signal0,signal1,...,signal8,这类代码的形式来模拟多个可变形参。
对于signaln,它继承自一个_signal_basen。_signal_basen 又继承自_signal_base。_signal_base这个根基类是必须的。
如果没有_signal_base这个根基类,那么上文中的m_senders里面应该保存什么呢?signal0类的指针?signal1类的指针?
明显都不是。m_senders需要保存某个类的指针,这个指针能在运行期运行指定的函数(这个函数会需要断开slot对象和signal对象的连接)。
如上例子,类A的对象a,同时连接了signal0<>的两个对象,还连接了signal1<int>的一个对象。所以当对象a被析构时,需要断开signal0<>和signal1<int>这两个类的连接,这样m_senders中需要保存signal0<>和signal1<int>这两种类型的指针。如果没有_signal_base这个根基类,我们根本就没有办法完成这个需求。
很明显,继承,是解决这个问题的正确漂亮简单的编码方式:
04 | virtual void slot_disconnect (has_slots<> *p_boj) = 0; |
06 | class signal0<> : public _signal_base |
09 | void slot_disconnect (has_slots<> *p_obj); |
11 | class signal1< int > : public _signal_base |
14 | void slot_disconnect (has_slots<> *p_obj); |
18 | for (set_iterator beg = m_senders.begin (); beg != m_senders.end (); ++beg) |
23 | (*beg)->slot_disconnect ( this ); |
_connect_base0和_signal_base0之间的多对一关系
这个关系很容易理解:每一个信号都需要包含多个slot。而slot是通过connect系列的类实现的。signal通过list容器包含了
多个connect_basen对象,实现了signal和slot之间的一对多关系。
signal通过connect系列类调用所连接的对象成员函数。connect实现了slot。
connect_base类的继承关系和connect_base类如何实现slot
要了解connect_base类ruuhe实现slot,首先我们需要了解C++中的成员函数指针。
这里不打算深入讲解C++的成员函数指针,不过希望你能理解如下代码:
07 | typedef void (A::*AMemFunc) (); |
09 | AMemFunc ptr_mem_func = &A::f; |
很明显,成员函数指针提供了运行期调用不同函数的功能。
为了实现slot,编译期需要记录类型信息。如下代码是不能通过编译的:
这就是_connection0模板的作用:
通过_connection0模板,编译器记录了类型信息,使得_connection0的emit函数中才可能正确的调用函数。
不过很可惜,模板实例化出来的每个类都是不相关的,意味着,我们无法使用容器包含一下两个对象:
因为a和b其实是毫无关系的两个对象,所以_connection0需要一个共同的基类:
1 | class _connection_base0 |
3 | virtual void emit () const = 0; |
6 | class _connection0 : public _connection_base0 |
这就是_connect系列中必须出现继承关系的原因。
代码细节
待续,虽然sigslot的整体框架比较简单,但是代码细节中考虑到的事情还是比较多的。不过对于一个有志向研究sigslot代码的人来说,前面的分析已经足够了。
最后
以上就是腼腆发卡为你收集整理的sigslot源代码分析解释一下这个UML图代码细节的全部内容,希望文章能够帮你解决sigslot源代码分析解释一下这个UML图代码细节所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复