上次和大家分享的是Qt信号和槽的一些宏定义以及元对象编译器。这次和大家分享信号和槽的connect函数到底连接了什么、还有元对象编译器都生成了什么代码。
很多讲Qt信号和槽的文章都会讲到元对象编译器生成的代码,也就是以moc_为前缀的.cpp文件,本例子中是在编译后Debug目录的moc_counter.cpp文件。当时我看了好多遍,最后还是没有很好的理解,随后就顺着信号和槽的执行过程进行理解。
一、connect
使用信号和槽之前,必须使用connect将信号和槽连接起来,那么在内部究竟都做了些什么呢?
首先connect是一个重载函数,我们最常使用的则是四个参数的调用,即默认连接类型为AutoConnection,也就是下面这个样子:
QMetaObject::Connection connect(const QObject * sender, const char * signal, const QObject * receiver, const char * method, Qt::ConnectionType type = Qt::AutoConnection)
之前说过SIGNAL和SLOT关键字,也就是说上次例子中的连接其实是这个样子:
QObject::connect(&a, 2signal_valueChanged(int), &b, 1slot_setValue(int));
OK,接下来看一个连接所需要的信息,下面是一个简化过的数据。Qt更高的版本中又对数据进行了抽象。
struct QObjectPrivate::Connection{ QObject *sender; QObject *receiver; union { StaticMetaCallFunction callFunction; QtPrivate::QSlotObjectBase *slotObj; }; // 单独连接的 ConnectionList 的 next 指针 Connection *nextConnectionList; // senders 链表 Connection *next; Connection **prev; QAtomicPointer argumentTypes; QAtomicInt ref_; ushort method_offset; ushort method_relative; uint signal_index : 27; // 信号范围(参考 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_ 赋值为 2,以便内部列表使用,同时供 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; } }};
可以查一查C++中结构体与类的区别与联系。
简单对里面的部分数据进行说明,如果遇到元对象编译器生成的代码再做扩展说明。
首先一个连接里面会存储发送对象与接收对象;
callFunction 是接收端的私有静态函数 qt_static_metacall()【Q_OBJECT宏中定义,由元对象编译器实现】,这个接收端私有静态函数是可以根据相对序号(索引)调用元方法的。该方法通过对索引计算就可以访问到对应的槽函数了,以后有机会再做详解。
元对象编译器扩展部分:
相对序号(索引)与元方法:
在每一个 QMetaObject 中,槽、信号以及其它该对象可调用的函数都会分配一个从 0 开始的索引。它们是有顺序的,信号在第一位,然后是槽,最后是其它函数。这个索引在内部被称为相对索引。它们不包含父对象的索引。
Qt中常见的元方法就是信号和槽,其他不做扩展。
nextConnectionList 是下一个链表节点,由同样的Connection 填充;
最终会构成如下的结构:
一个对象可能有多个信号,Qt的MedaObject会把信号组装成一个Signal Vector,每个signal都可以根据序号(索引)来获得;每个信号都会维护一个要触发槽的链表,当一个Signal被调用时,对应链表内所有的槽都会被触发【以前看过当一个信号连接多个槽时,槽是被随机触发的,通过源码来看,一个信号对应的多个槽是按加入链表顺序来触发的,有机会做下验证】。
二、小结
简单来说,一个connect就是存储了接口类(QObject)的一些信息,通过这些信息以及Qt的对象树模型再加上序号索引就可以快速找到一个信号所连接接口对象对应的槽函数。
这里面没和大家说字符串表以及内省表,有兴趣的话大家可以想一想这两个表在Qt元对象系统中究竟扮演了什么角色。