qt源码--信号槽

本篇主要从Qt信号槽的连接、断开、调用、对象释放等方面展开;

1.信号建立连接过程

connect有多个重载函数,主要是为了方便使用者,比较常用的有2种方式:

a. QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);

b. QObject::connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));

 a方式对应的源码如下:

 

 

SIGNAL、SLOT对应的宏定义:

所以以上代码展开后为:QObject::connect(&timer, "2timeout()", &loop, "1quit()");

此函数除了此部分外,其他均是根据函数参数查找到信号、槽在sender、receiver的QMetaObject中的偏移量,特别需要注意的地方是链接参数使用Qt::QueuedConnection时,会检测参数对应的类型:

 queuedConnectionTypes函数源码如下:

 当时用自定义类型时,除了需要使用Q_DECLARE_METATYPE及qRegisterMetaType外,还需而外注意传参类型的完全匹配,即如果参数类型const Custom &时,qRegisterMetaType则对应为:qRegisterMetaType<const Custom &>(),主要原因是按照字符串进行匹配,否则会打印Cannot queue arguments of type ‘xxx’,Make sure ‘xxx’ is registered using qRegisterMetaType警告。

接下来主要看一下QMetaObjectPrivate::connect函数;

 其信号与槽主要是通过QObjectPrivate::Connection结构进行存储,其对应的源码如下:

 

 而外需要注意的是以上标记红色框的部分:

其信号和槽的连接过程对应3个链表:

1、QObjectConnectionListVector *connectionLists顺序列表管理每一个信号对应的Connection对象

2、Connection *nextConnectionList管理每一个信号对应的多个接收者,即一个信号可以绑定多个对象

3、Connection *senders和Connection *next,Connection **prev用来管理每一个信号对应的多个发送对象,即一个类有多个对象,每个对象都有可能发送此信号

 基本上链表关系如上所示,其addConnection的过程就是建立3个链表之间的关系,如果连接类型是Qt::UniqueConnection,则指挥建立一次连接

 此函数剩余部分为构建Connection对象,并使用q指针,建立链表连接

 

 QObjectConnectionListVector对象会根据信号在对象中的绝对偏移量动态扩展其大小,即申请顺序链表;

每次建立连接过程,会检测下链表,确认是否需要清除接收对象为空的对象;

connectedsignals采用了简单算法记录其信号是否建立连接;

2.信号断开过程:

其主要是通过disconnectHelper函数来断开连接

其主要是将receiver指针更改为空指针,并修改senders链表;

3、调用过程

qt定义的信号,会通过moc解释器生成对应的moc文件,最终调用activate函数(moc文件的格式即信号在moc文件中的展开形式在前几节中已解释),其对应源码如下:

 activate函数整体较长,其主要是定义2个辅助类:ConnectionListsRef、QConnectionSenderSwitcher,及遍历信号列表,执行所有的槽函数;

ConnectionListsRef函数主要是利用构造函数及析构函数来确保connectionLists对象在使用期间不被删除;

在构造函数中增加inUse计数器,标识其正在被使用,如果在试用期间,删除此对象,则connectionLists对象会被延迟删除,并在其析构函数中执行delete;通过定义局部变量的方式,确保析构函数中的代码一定会被执行;

QConnectionSenderSwitcher也是利用此方法,

 其主要是给receiver对象的currentSender对象进行赋值,并在结束后恢复currentSender对象。

 signal_begin_call、signal_end_callback是定义的钩子函数,主要是在执行信号与槽时,可以自定义一些执行逻辑,比如打印日志信息等;

 上图1,先获取对应的信号列表,由于信号列表是通过数组实现的顺序列表,所以直接通过下表进行访问;

上图2,先判断信号发送者与接收者是否在同一个线程中,如果不在同一个线程,且连接类型为auto或QueuedConnection类型,则将执行的事件放到队列中执行(qt槽的执行是通过QMetaCallEvent事件来实现的);如果连接类型是BLockingQueueConnection类型,且在同一个线程中,则会造成死锁,BLockingQueueConnection实现主要是通过定义Qsemaphore信号来实现线程同步的,如果在同一个线程,arquire会阻塞当前线程,且QMetaCallEvent事件无法被执行,导致信号量不能被释放。

 上图3,直接调用metacall函数,其对应是调用qt_metacall函数,其实现是在moc文件中有其对应的实现过程(已在QMetaObject中解释)。

 另外其槽函数的调用还有2中方式,一种是通过QSlotObject类,另一种是定义函数指针的形式,由于在工作中主要是通过qt的moc文件生成对应的槽函数,所以在此不在赘述;

4、释放过程

根据信号连接的过程,猜测其主要操作有2中情况;1是作为发送者,需要断开所有的信号连接;2是作为接收者,需要将所有发送者中的receiver对象置为空。

其源码如下:

 

上图1,主要是遍历所有的信号,并将其receiver对象 置为空,如果receiver对象不为空,则需要修改seners链表,删除此对象在senders链表中的节点;而外注意一下代码:

如果connectionLists->inUse不为零时,只是将connectionLists->orphaned置为true,且将connectionLists置为空,避免了其在信号调用过程中多次删除的问题,即connectionLists对象延时删除。

上图2,主要是遍历所有的senders对象,将所有发送者对象的receiver对象置为空,并将dirty置为空,在执行连接或删除信号时,刷新信号列表;

总结:

1、信号与槽的连接主要是3个链表的实现,顺序链表,管理对象中的所有信号;每一个信号有其独立的信号列表,管理所有的接收者对象;信号发送对象链表,管理所有的发送者对象;

2、其槽函数的调用是通过QMetaCallEvent时事件来实现的,通过postEvent发送;直接调用时利用静态函数metacall中转,最终调用moc文件中的qt_metacall函数实现;

3、利用inUse计数器,实现connectionList延时删除的效果,源码中利用大量的ref计数器方式,有效避免了对象的多次删除及野指针的问题。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值