
connect(Sender,SIGNAL(signal),Receiver,SLOT(slot))
这四个参数分别是发送者对象、发送者对象发送的信号、接收者对象、接收者对象响应该信号的槽函数,所以我们有可能认为该函数就只有四个参数,但实际上是有第五个参数的,只是通常该函数已经给第五个参数赋值了而已,我们所使用的是默认值。实际上connect函数应该是如下形式:
bool QObject::connect ( const QObject * sender, const char * signal, const QObject * receiver, const char * method, Qt::ConnectionType type = Qt::AutoConnection )
Qt::DirectConnection参数 参数含义
Qt::AutoConnection 默认值,使用这个值则连接类型会在信号发送时决定。如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。
Qt::DirectConnection 槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。效果看上去就像是直接在信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成奔溃。
Qt::QueuedConnection 槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这个。
Qt::BlockingQueuedConnection 槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。
Qt::UniqueConnection 这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是避免了重复连接。
sender和receiver是QObject对象指针,函数里面我们用到了Qt提供的两个宏SIGNAL()和SLOT()这是Qt要求的,要关联信号和槽必须借助于这两个宏,两个宏的定义如下:
#define SLOT(name) "1"#name
#define SIGNAL(name) "2"#name
通过这两个宏,就可以把我们传递进去的槽和信号的名字转化成字符串,并在这两个字符串前面加上附加的字符。Qt5又在此基础上扩展了一种写法不必用到两个宏SIGNAL()和SLOT(),而是直接写&类名::信号或者&类名::槽函数。一个信号可以和多个槽相连;也可以多个信号可以连接一个槽;也有一个信号可以连接到另一个信号;一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽,有时候我们需要手动去断开连接,如下情况:
有时我们程序中某些情况下某个 *** 作需要断开这个信号槽连接, *** 作结束后有需要重新连接,断开连接时,那我们需要调用函数
bool QObject::disconnect ( const QObject * sender, const char * signal, const QObject * receiver, const char * method )
用法和connect大致相同。
Sender
Sloter
Connection
那么Qt是怎么将这两个信号槽链接起来的呢?接下来看一看connect的源码:
在Qt中connect有很多个重载,大致可以分为2类:一种是qt4的Sender(),Slot()方式,另一种是qt5的模板方式进行链接
用这种方式链接会调用上图中的第一个connect函数,其内部的实现如下(太多了我们分段看看)
这一部分主要是在检测传入的信号是否属于sender,不是的话会告警并直接返回,这也是这种方式不好的地方,必须写正确,而且写错了如果不看告警的话是不知道的。
这里面两个主要的函数:
我们看看他们是如何实现的
通过Qt的元对象(QMetaObject)获取,到这里需要讲一讲QMetaObject是个什么东西,它是个类,内部有一个结构体d如下:
OK,继续回去看staticMetaObject这个成员的初始化,我们在这里先只关注stringdata和data这个两个参数,这里用到了,后面会继续有其他参数的意义,在这个地方stringdata被初始化成了qt_meta_stringdata_TestSender.data,data被初始化成了qt_meta_data_TestSender。它们是啥,还是在moc文件里
这两个一个保存着信号的名字,一个保存着信号的返回值,参数类型和参数个数。
到这里就可以通过这两个参数和链接时传入的signal字符串对比找到信号在sender中的的位置,当然因为继承的存在,所以这个位置不是真是的位置,还需要找到signalOffset这个函数找到在这个类中的位置。怎么找的呢?其实MetaObject中的data最终会被转换成这个
因为QObject有2个信号,所有这个地方获取到的索引位置为3
这一段跟上面差不多,用来检车receiver和槽函数是否对应
这个地方主要是检查信号槽的参数(比如信号的参数不能比槽的参数少等)
接下来就会进入另一个connect函数,他的实现
到这里就要看到信号槽是如何链接起来的了,每个QObject的对象都会维护着多个Connection的链接对象,这里面存储着信号的发送者、信号的接收者等一系列信息
至此信号差就链接起来了,最终会以Connection对象的方式存放在每个QObject对象的connectionlist里面,这种是Qt4老的链接方式
Qt5的新链接方式相比Qt4要简单很多,因为是基于模板实现的,因此可以直接找到函数的地址,最终也会放到链接列表里,来看看与Qt4的区别吧。
不知道是否还记得最开始connect函数的几种重载,这里用到的是下面红框里的方式(模板)
让我们先看一种
它会调用这个实例化的模板函数
看到了吧,这个函数有2个功能,一个是执行信号(因为信号可能会很信号链接,具体怎么执行到的后面会讲),另一个是获取信号函数在对象的偏移量(如何获取的呢,其实就是对比传入的信号函数的地址和实际信号的地址,这里a0就是要获取的偏移量,a1就是信号函数的地址)
这个地方需要注意的是connecImpl与connect不同的是吧receiver对象包装成了一个QSlotObject的对象,它是这样的
这个类接受一个QtPrivate::FunctionPointer<Func>的对象,这个是个啥?着其实是个模板类,用来识别槽函数的地址,在connect的时候也用到了这个,具体怎么实现的可以放出来,就是模板的推导比较麻烦,可以简单的理解成通过这个可以判断槽函数是否是receiver的成员函数
接着往下讲,不知道是否还记得之前的connectImpl函数,它获取到了信号的偏移量,接着又调用了一个connectImpl,它是这样的
看到这里有没有熟悉的感觉,跟Qt4的其实差不多,也是将信号槽存储到connection对象内,在添加到sender的connectionList里,不过这里存储的槽不是一个函数,而是一个QSlotObject对象的地址,看看Connection结构的内容可以发现使用Union实现的,存储的都是一个地址
到这里就讲完了Qt4和Qt5信号槽的链接部分,不知道是否讲的明白,moc文件里生成的东西基本就是Qt信号槽的核心,每一个都有其作用,都对应着QObjcet底层的数据,也是Qt元对象的核心。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)