2022-9-21 更新,参考CSDN-epollet文章
2022-7-29 更新,参考Qt进阶之路-武威的涛娃
函数原型
1 | QObject::connect(const QObject *sender, const char *signal, |
连接的方式
通过函数名
1 | ... |
其中 SIGNAL() 与 SLOT() 为定义的宏,如下。
1 |
通过这两个宏,可以把传入的信号与槽的名字转化为字符串,并区别开来。
优点
当存在多个信号或槽重载时,可以很明显的区分调用。
缺点
写法较为复杂,写时注意括号。
通过函数地址
1 | connect(socket, &QLocalSocket::connected, this, &MainWindow::handleConnected); |
优点
写法简洁。
缺点
遇到重载时,编译器会报错。
1 | // error! |
通过函数指针
可以解决上述情况
通过static_cast
1 | connect(socket, static_cast<void (QLocalSocket::*)(QLocalSocket::LocalSocketError)>(&QLocalSocket::error), this, &MainWindow::handleError); |
通过QOverload模板类
1 | connect(btn1, QOverload<QLocalSocket::LocalSocketError>::of(&QLocalSocket::error), this, &MainWindow::handleError); |
通过qOverload函数模板
1 | // C++14 support |
通过Lambda表达式
1 | connect(btn1, SIGNAL(clicked()), this, [=](bool)->void |
优点
简单明了,即用即定义,不用在类声明中添加槽函数。
Qt::ConnectionType
ConnectionType 由5个枚举值组成。
- Qt::AutoConnection = 0
1 | 默认值,如果receiver位于发出信号的线程中,则使用 Qt::DirectConnection;否则,使用 Qt::QueuedConnection。 |
- Qt::DirectConnection = 1
1 | 槽函数运行于信号发送者所在的线程,效果上就像是直接在信号发送的位置调用了槽函数。 |
- Qt::QueuedConnection = 2
1 | 槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号后,槽函数不会立即被调用,等到接收者当前函数执行完,进入事件循环之后,槽函数才会被调用。 |
- Qt::BlockingQueuedConnection = 3
1 | 与 Qt::QueuedConnection 相同,只是信号线程阻塞直到槽返回。 |
- Qt::UniqueConnection = 4
1 | 当此类型为当某个信号和槽已经连接时,在进行重复连接时就会失败,可避免重复连接。 |
注意事项
- 一个信号可以和多个槽相连(槽会一个接一个地被调用,调用顺序与连接顺序一致);
- 多个信号可以连接一个槽(只要任意一个信号产生,这个槽就会被调用);
- 一个信号可以连接到另一个信号(当第一个信号发送时,第二个信号则立刻发送)。
- 当不再使用时建议断开连接(虽然当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽),一是为了代码规范,二是防止在距离销毁这段时间再次收到信号。
connect的返回值
connect
的返回值为QMetaObject::Connection
,代表一个连接。
通常情况下不用去处理,一般用于验证连接是否有效及断开连接。
但如果使用signal-functor
格式连接,没有对象存在,则需要使用此返回值进行断开。
多种连接方式
除了第一种字符串式信号槽外,Qt还支持更多种连接方式:
元方法式
1 | // [1] 字符串式信号槽 |
2.的调用方式是将信号槽的字符串转化为了QMetaMethod
,一般不会使用;
3.中的调用方法是对1.的重载,非静态成员函数,有this
指针,省略掉了receiver
参数。
函数指针式
1 | // [4] 连接信号到qobject的成员函数 |
[4]的用法也比较多,类似于
1 | MyClass c; |
且[4]的实现形式相比[1]更快一点,省去了字符串查找的过程,但建立连接后是没有区别的。
- 在一些需要”运行期反射”的情况下(头文件都没有,只知道有这么个对象,和函数的名字),只能用[1];
- [5]可以连接信号到任意非成员函数指针上。除了槽函数,普通的函数也可以连接。这种连接不支持设置连接类型,可以看作是单纯的函数调用;
- [6]是对connect[5]的重载,增加了一个
context
对象代替reveicer
对象的作用。这种连接是可以设置连接类型的。
functor式
首先,什么是functor
?
在C++11之前, Qt通过自己的实现来推导函数指针及其参数,即QtPrivate::FunctionPointer, 用来处理信号-槽的连接。
C++11带来了lambda
, 以及std::bind
和std::function
, std::function
本身可以存储lambda
、std::bind
以及FunctionPointer
。
这时候Qt已有的[4]、[5]、[6]是可以支持FunctionPointer
的,而新出现的lambda
以及std::bind
是不支持的,
QtPrivate::FunctionPointer
推导不出这些类型。所以Qt把这些不支持的新类型(主要是lambda
和std::bind
)称为functor
(文档和源码都这么命名),并增加了[7]和[8]以支持functor
。
1 | //[7] 连接信号到任意functor |
[7]可以将任意信号连接到lambda
和std::bind
上;
[8]是对[7]的重载,增加了一个context
对象代替reveicer
对象的作用。这种连接是可以设置连接类型的。
传值与引用
先直接说结论吧:
- Qt为了避免队列连接造成的悬挂引用问题,会拷贝一次参数。跨线程调用槽(且是Qt::QueuedConnection)的话这一次拷贝无法避免。
- 能传引用就传引用,避免参数多两次拷贝。