广州双城热恋创意文化传播有限公司

现在的位置: 主页 > 公司荣誉 > 文章列表

文章正文

从零开始实现信号槽机制:二

作者:广州双城热恋创意文化传播有限公司 来源:www.999loveyou.com 发布时间:2017-09-13 13:26:11
从零开始实现信号槽机制:二

好了,是时候写段Qt代码看看了,这是一段典型的使用Qt信号槽的代码,因为我们这段代码直接写在main.cpp里面,所以在最后记得加上#include main.moc:

#include #include using namespace std; class Button : public QObject { Q_OBJECT public: void nowClick(bool b) { emit click(b); } signals: void click(bool); }; class Tv : public QObject { Q_OBJECT public: Tv(int b, int t) : bootTime(b), offTime(t){} protected slots: void onStateChanged(bool b) { if ( b == true ) cout << Tv is being turned on. bootTime is << bootTime << endl; else cout << Tv is being turned off. offTime is << offTime << endl; } private: int bootTime; int offTime; }; int main(int argc, char *argv[]) { QApplication a(argc, argv); Button btn; Tv tv(10, 20); QObject::connect(&btn, SIGNAL(click(bool)), &tv, SLOT(onStateChanged(bool))); btn.nowClick(true); return a.exec(); } #include main.moc
我们知道Qt源代码在make之前需要先进行一道qmake,而qmake会调用moc.exe这个“元对象编译器”来对包含Q_OBJECT宏的文件进行一个预处理,要弄清Qt的信号槽如何运作,首先我们得知道signals,slots这种不符合C++规范的东西到底被处理成了什么鬼,ok,我们在qobjectdefs.h里面找到它们了: #define slots #define signals public // Qt5 中由 protected 改为 public 以支持更多特性 #define emit #define SLOT(a) 1#a #define SIGNAL(a) 2#a
当然这些定义在某些情况下,比如定义了QT_NO_EMIT时会不同,不过这超出了本文的讨论范围,大家有兴趣可以去读下源码。好的,现在我们知道了,“slots”和“emit”根本就是两个空宏,而signals仅仅是一个public,这样看来,Qt中的信号是个真正的函数无疑,而不是像sigslot中以functor的方式实现。connect函数则是为信号和槽函数添加了一个数字并将其转化成字符串,也就是,上面的connect实际等价于: QObject::connect(&btn, 2click(bool), &tv, 1onStateChanged(bool));
这种链接方式是可以通过编译并得到正确结果的,但我们现在还不能删掉signals,和slots关键字——它们不是定义为空吗,为什么不能删呢?很简单,signals定义的函数我们根本没有实现,它只有一个声明。既然能通过编译,说明Qt在把signals改为空之前必定还做了些其他的事情。翻看main.moc,我们果然发现了这个信号函数的实现: // SIGNAL 0 void Button::click(bool _t1) { void *_a[] = { 0, const_cast(reinterpret_cast(&_t1)) }; QMetaObject::activate(this, &staticMetaObject, 0, _a); }
Qt不管该信号的参数类型与参数个数,将其统一转成了void*并存在数组中,传递给QMetaObject::activate()进行调用。到这里你也许会疑惑了,我们在前面的设计中被参数问题弄到头大,槽函数收到这堆二进制数据以后如何知道该用哪种格式来解释它们呢?

好的,active()这个函数后面再说,我们先弄清楚QMetaObject是个神马,它是理解Qt信号槽机制的关键所在。贴段官方介绍吧:

“QMetaObject类包含Qt对象的元信息。Qt的元对象系统负责信号和槽的通信机制,运行时类型信息,和Qt的属性系统。每个QObject的子类将被创建一个QMeteObject实例,并被应用于应用程序中,这个实例存储了该QObject子类所有的元信息,通过QObject::metaobject()可以获取它。”

也就是说,基于QMetaObject,我们可以获取QObject子类对象的类名、父类名、元方法(信号、槽和其他声明为INVOKABLE的方法)、枚举、属性、构造函数等诸多信息。而QMetaObject中的数据则是来自于moc对源文件所进行的词法分析。看看我们main.moc中Qt为我们的Button类生成的整型数组:

static const uint qt_meta_data_Button[] = { // content: 7, // revision 0, // classname 0, 0, // classinfo 1, 14, // methods 0, 0, // properties 0, 0, // enums/sets 0, 0, // constructors 0, // flags 1, // signalCount // signals: name, argc, parameters, tag, flags 1, 1, 19, 2, 0x06 /* Public */, // signals: parameters QMetaType::Void, QMetaType::Bool, 2, 0 // eod };

// content栏目中的13个整型数表示的信息已由注释给出,对于有两列的数据,第一列表示该类项目的个数,第二列表示这一类项目的描述信息开始于这个数组中的哪个位置(索引值)。可以看到Button类包含一个方法信息(nowClick()非INVOKABLE方法不被记录),就是我们的信号了,并且该方法的描述信息开始于第14个int数据。

// signals注释下那个“1”即为qt_meta_data_Button[14],注释写得更清楚,表明这里开始记录的是(信号方法)信息,每个方法的描述信息由5个int型数据组成。分别代表方法名、该方法所需参数的个数、关于参数的描述(表示与参数相关的描述信息开始于本数组中的哪个位置,也是个索引)、以及tag和flags。最后,该数组存放了方法的返回类型、每个参数的类型、以及参数的名称。也就是说,任何一个可以拿到Button类的父类指针(QObjcet*)的对象都可以清楚地了解其signal的所有信息。

是时候建立连接了

我们现在已经知道,基于元对象系统,Qt可以通过名称很快地找到对应的方法的索引,然后,我们还需要一个用来管理连接的类,由于Qt中的QObject类即可以作为接收者也可以作为发送者,因此这个Connection需要同时包含发送对象与接收对象的指针,以及对应信号与槽函数的索引。Qt在QObjectPrivate中定义了这个Connection,位于qobject_p.h中:

struct Connection { QObject *sender; QObject *receiver; union { StaticMetaCallFunction callFunction; QtPrivate::QSlotObjectBase *slotObj; }; // The next pointer for the singly-linked ConnectionList Connection *nextConnectionList; //senders linked list Connection *next; Connection **prev; QAtomicPointer argumentTypes; QAtomicInt ref_; ushort method_offset; ushort method_relative; uint signal_index : 27; // In signal range (see QObjectPrivate::signalIndex()) ushort connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking ushort isSlotObject : 1; ushort ownArgumentTypes : 1; Connection() : nextConnectionList(0), ref_(2), ownArgumentTypes(true) { //ref_ is 2 for the use in the internal lists, and for the use in QMetaObject::Connection } ~Connection(); int method() const { return method_offset + method_relative; } void ref() { ref_.ref(); } void deref() { if (!ref_.deref()) { Q_ASSERT(!receiver); delete this; } } };
每个Sender需要维护一个QVector,其长度是其signal的个数,QVector内每个单元存放着一个Connection链表的头结点,存储该信号的每个链接;而Receiver则相对简单一些,它直接维护一个Connection链表,表示所有链接到它身上的链接。当然,一个QObjcet可能即是Sender又是Receiver,因此一个QObject可能同时在维护这两个数据结构,并且,如果是该QObjcet内部的信号槽调用,两个Connection对象将会重叠,贴张国际友人的图像像下面这样:

http://www.2cto.com/

企业建站2800元起,携手武汉肥猫科技,做一个有见地的颜值派!更多优惠请戳:天门SEO http://tianmen.raoyu.net

COPYRIGHT © 2015 广州双城热恋创意文化传播有限公司 ALL RIGHTS RESERVED. 网站地图 技术支持:肥猫科技
精彩专题:网站建设
购买本站友情链接、项目合作请联系客服QQ:2500-38-100