QObject
QObject::connect的5种用法
1. 使用SIGNAL()和SLOT()宏(Qt4)
class myobject : public QObject
{
Q_OBJECT
public:
myobject(QObject *parent = nullptr):QObject(parent){
}
signals:
void signal1(int i);
public slots:
void slot1(int i){
qDebug()<<"slot1,i="<<i;
}
};
#普通函数
void funslot(int i)
{
qDebug()<<"fun:i="<<i;
}
myobject *obj1 = new myobject("boj1");
myobject *obj2 = new myobject("boj2");
QObject::connect(obj1, SIGNAL(signal1(int)), obj2, SLOT(slot1(int)),Qt::AutoConnection);
QObject::connect(obj1, "2signal1(int)", obj2, "1slot1(int)",Qt::AutoConnection);
emit obj1->signal1(123);
注意,信号和槽参数不能包含任何变量名,只能包含类型。名称错误编译阶段无法检测出来
#define SLOT(a) “1”#a
#define SIGNAL(a) “2”#a
最终加入到发送者connect链表里面的连接:
QMetaObjectPrivate::connect{
c->method_relative = method_index;
c->method_offset = method_offset;
c->connectionType = type;
c->isSlotObject = false;
c->argumentTypes.storeRelaxed(types);
c->callFunction = callFunction;
}
Connection保存在发送者的SignalVector[signal_index]里面其数据结构层级为:
QObject->QObjectPrivate->ConnectionData->SignalVector->ConnectionList->QObjectPrivate::Connection
SignalVector可以看成是个二维数组[signal_index][ConnectionList],
signal_index为信号函数对应的编号,其从0开始按照声明顺序递增0,1,2,3,4....
每一次connect就相当于在发送对象的SignalVector[signal_index]新增一个QObjectPrivate::Connection
每次disconnect就相当于在发送对象的SignalVector[signal_index]删除一个或多个匹配的QObjectPrivate::Connection
2. 使用类成员函数指针(Qt5新增,并推荐)
QObject::connect(obj1, &myobject::signal1, obj2, &myobject::slot1,Qt::AutoConnection);
QObject::connect(obj1, &myobject::signal1, &myobject::slot1);
QObject::connect(obj1, &myobject::signal1, funslot);
注意,信号和槽地址错误,编译阶段可以检测出来
有重载的情况怎么办?QOverload或static_cast转换解决
最好的做法可能是建议不要重载信号或槽
QObject::connect(obj1, &myobject::signal1, obj2, QOverload<float>::of(&myobject::slot1),Qt::AutoConnection);
QObject::connect(obj1, &myobject::signal1, static_cast<void (myobject::*)(int)>(&myobject::slot1));
优点
- 编译时检查是否存在信号和插槽,类型,或者是否缺少Q_OBJECT。
(但是如果信号参数填的是非信号函数,编译阶段检查不出来,运行时报错signal not found)
QObject::connect(obj1, &myobject::slot1, &myobject::slot1);
//QObject::connect: signal not found- 参数可以通过typedef定义,也可以使用不同的名称空间说明符,并且可以使用。
- 如果存在隐式转换(例如,从QString到QVariant),则可以自动转换类型。
- 可以连接到QObject的任何成员函数,而不仅仅是插槽。
- 可以与C++11 lambda表达式一起使用
缺点
- 更复杂的语法(您需要指定对象的类型)
- 重载载情况下的语法非常复杂
- 不再支持插槽中的默认参数。
最终加入到发送者connect链表里面的连接:
std::unique_ptr<QObjectPrivate::Connection> c{new QObjectPrivate::Connection};
c->sender = s;
c->signal_index = signal_index;
c->receiverThreadData.storeRelaxed(td);
c->receiver.storeRelaxed(r);
c->slotObj = slotObj;
c->connectionType = type;
c->isSlotObject = true;
if (types) {
c->argumentTypes.storeRelaxed(types);
c->ownArgumentTypes = false;
}
3. 关联函数
QObject::connect(obj1, &myobject::signal1, funslot);
最终加入到发送者connect链表里面的连接QObjectPrivate::Connection
同使用类成员函数指针
4. Lambda
QObject::connect(obj1, &myobject::signal1, [](int i){
qDebug()<<"Lambda:i="<<i;
});
QObject::connect(obj1, &myobject::signal1,obj2, [](int i){
qDebug()<<"Lambda:i="<<i;
},Qt::AutoConnection);
最终加入到发送者connect链表里面的连接QObjectPrivate::Connection
同使用类成员函数指针
5. QMetaMethod 很少用到
QMetaMethod sig = obj1->metaObject()->method(obj1->metaObject()->methodOffset()); // void signal1(int i);
QMetaMethod slt = obj2->metaObject()->method(obj2->metaObject()->methodOffset()+1);//void slot1(int i);
QObject::connect(obj1, sig, obj2, slt, Qt::AutoConnection);
可以根据QObject的metaObject()方法获取元对象QMetaObject,然后根据元对象的method方法获取信号或槽的元方法对象QMetaMethod
元对象QMetaObject的methodOffset()函数获取当前对象的第一个信号的位置(不一定为0),
如果没有信号就是第一个槽的位置,按照信号槽函数的声明顺序来的,信号在前面槽在后面
可以打印出来观看:
for(int i = obj1->metaObject()->methodOffset(); i < metaObject->methodCount(); ++i)
qDebug() << QString::fromLatin1(metaObject->method(i).methodSignature());
Qt::ConnectionType
1. Qt::AutoConnection
<(默认)如果接收器位于发出信号的线程中,则使用Qt::DirectConnection。否则,使用Qt::QueuedConnection。在发出信号时确定连接类型。
receiverInSameThread判断发送信号对象和接收的槽对象是否在同一个线程
if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
|| (c->connectionType == Qt::QueuedConnection))
{
QMetaCallEvent *ev = new QMetaCallEvent(c->slotObj, sender, signal, nargs);
QCoreApplication::postEvent(receiver, ev);
}
else if (c->connectionType == Qt::BlockingQueuedConnection)
{
//信号量会被放入事件里面,在事件析构的时候release
QSemaphore semaphore;
{
QBasicMutexLocker locker(signalSlotLock(sender));
QMetaCallEvent *ev = new QMetaCallEvent(c->slotObj, sender, signal_index, argv, &semaphore);
QCoreApplication::postEvent(receiver, ev);
}
semaphore.acquire();
}else
{
QMetaObject::metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv);
}
bool QObject::event(QEvent *e)
{
switch (e->type()) {
case QEvent::MetaCall:
{
QAbstractMetaCallEvent *mce = static_cast<QAbstractMetaCallEvent*>(e);
QBasicMutexLocker locker(signalSlotLock(this));
mce->placeMetaCall(this);
break;
}
}
}
void QMetaCallEvent::placeMetaCall(QObject *object)
{
QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod,
d.method_offset_ + d.method_relative_, d.args_);
}
QAbstractMetaCallEvent::~QAbstractMetaCallEvent()
{
#if QT_CONFIG(thread)
if (semaphore_)
semaphore_->release();
#endif
}
2. Qt::DirectConnection
当发出信号时,立即调用该槽。如果槽函数对象与信号不在同一线程,槽函数也会在信号线程中执行。
3. Qt::QueuedConnection
当控制返回到接收者线程的事件循环时调用该槽。槽在接收者的线程中执行。
4. Qt::BlockingQueuedConnection
与Qt::QueuedConnection相同,除了信号线程阻塞直到插槽返回。
如果接收方位于信号线程中,则不能使用此连接,否则应用程序将死锁。
5. Qt::UniqueConnection
这是一个标志,可以使用位或与上述任何一种连接类型组合使用。当Qt::UniqueConnection设置时,如果连接已经存在,QObject::connect()将失败(即,如果相同的信号已经连接到同一插槽的同一对对象)。这个标志是在Qt 4.6中引入的。
信号与槽
1. 信号的定义
使用关键字:signals 加冒号, 后面声明的函数就是信号,其本质就是函数
其被宏定义为:# define signals public qt_signal
信号不需要也不可以由我们自己实现,因为其会被moc编译器编译成函数
*信号与槽的返回值要兼容
signals: //等于 public qt_signal :
void signal1(int i);
void signal2(int i);
void signal3(int i);
moc编译器编译后的myobject::signal1函数,实现如下:
void myobject::signal1(int _t1)
{
void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };
QMetaObject::activate(this, &staticMetaObject, 1, _a);
}
2. 信号发送
- 使用关键字emit,emit是空的宏定义: # define emit
- 信号函数调用前加emit,如 emit obj1->signal1(111);
- 由于emit是空的宏定义,所以也可以直接调用信号函数,obj1->signal1(111);
信号发送本质就是函数调用
一般为了区分是信号和普通函数,都会加上emit关键字,提高代码可读性
3. 槽函数
- 权限声明后加slots表示后面的为槽函数,如public slots:
- 槽函数不支持默认参数
class A:public QObject
{
Q_OBJECT
signals:
int signal2();
public slots:
int slot1(int i=100);
};
QObject::connect(obj1, &myobject::signal2, obj2, &myobject::slot1,Qt::AutoConnection);//不支持,编译时报错
QObject::connect(obj1, SIGNAL(signal2()),obj2,SLOT(slot1(int)),Qt::AutoConnection);//运行时报错
emit signal2();
disconnect
1. 取消所有信号关联的所有槽
```
QObject::disconnect(obj1, nullptr, nullptr, nullptr);
obj1->disconnect();
这同时也会取消lambda和普通函数的关联
```
2. 取消某个信号关联的所有槽
```
QObject::disconnect(obj1,SIGNAL(signal1()), nullptr, nullptr);
QObject::disconnect(obj1,&myobject::signal1, nullptr, nullptr);
obj1->disconnect(SIGNAL(signal1()));
这同时也会取消lambda和普通函数的关联
```
3. 取消某个信号关联的某个接收者的所有槽
```
QObject::disconnect(obj1,SIGNAL(signal1()), obj2, nullptr);
QObject::disconnect(obj1,&myobject::signal1, obj2, nullptr);
这个无法取消lambda和普通函数的关联(普通函数接收者为发送者本身)
QObject::disconnect(obj1,&myobject::signal1, obj1, nullptr);
这个可以取消lambda和普通函数的关联
```
4. 取消某个信号关联的某个接收者的某个槽
如果多个相同的关联都将被取消;
对于这种指定具体的信号和槽的关联取消:SIGNAL/SLOT关联的,必须SIGNAL/SLOT方式取消关联,类成员函数关联的比类成员函数取消关联
```
QObject::connect(obj1,SIGNAL(signal1(int)), obj2, SLOT(slot1(int)));
QObject::disconnect(obj1,SIGNAL(signal1(int)), obj2, SLOT(slot1(int)));
QObject::connect(obj1, &myobject::signal1, obj2, &myobject::slot1);
QObject::disconnect(obj1, &myobject::signal1, obj2, &myobject::slot1);
下面的将无法被取消:
QObject::connect(obj1,SIGNAL(signal1(int)), obj2, SLOT(slot1(int)));
QObject::disconnect(obj1, &myobject::signal1, obj2, &myobject::slot1);
下面的将无法被取消:
QObject::connect(obj1, &myobject::signal1, obj2, &myobject::slot1);
QObject::disconnect(obj1,SIGNAL(signal1(int)), obj2, SLOT(slot1(int)));
具体原因如下:
就是这两种不同方法添加的QObjectPrivate::Connection不一样,具体到槽的匹配永远无法找不到。
disconnect最终都将走到这一步进行匹配:
if (signal_index < 0) {//如果disconnect传入的signal为nullptr,删除所有信号的所有连接者
for (int sig_index = -1; sig_index < scd->signalVectorCount(); ++sig_index) {
disconnectHelper(..., signal_index, receiver, method_index, slot, ...);
}
}else
{
disconnectHelper(..., signal_index, receiver, method_index, slot, ...);
}
disconnectHelper实现如下
auto &connectionList = connections->connectionsForSignal(signal_index);//取出对应信号的连接链表
auto *c = connectionList.first.loadRelaxed();
while (c) { //遍历signal_index信号链表的所有接收者
QObject *r = c->receiver.loadRelaxed();//获取当前链表节点的接收者
if (r)
{
if(receiver == nullptr ){//如果disconnect传入的接收者为nullptr,直接删除连接者
connections->removeConnection(c);
}
else if(r == receiver)//如果不为空判断连接者是否相同
{
//使用类成员函数指针connect的method_index<0,并且是isSlotObject=true
//使用SIGNAL()和SLOT()宏connect的method_index>0,并且是isSlotObject=false
if((method_index < 0 || (!c->isSlotObject && c->method() == method_index))
{
if((slot == nullptr || (c->isSlotObject && c->slotObj->compare(slot))
{
connections->removeConnection(c);
}
}
//上面的判断总结为:
//如果 使用类成员函数指针connect的比较 c->slotObj->compare(slot)
//如果 使用SIGNAL方式connect的比较c->method() == method_index
//所以这两种不同的connect,disconnect时具体到槽的匹配永远无法找不到。
}
}
c = c->nextConnectionList.loadRelaxed();
}
```
5. 单独取消关联的某个普通函数和Lambda表达式
使用连接时返回的QMetaObject::Connection变量,取消关联对应连接
```
auto func =QObject::connect(obj1,&myobject::signal1,funslot);
QMetaObject::Connection lambdac = QObject::connect(obj1,&myobject::signal1,[](int a){
qDebug("lambda %d\n",a);
});
QObject::disconnect(func);
QObject::disconnect(lambdac);
```
6. 取消某个接收者对象的所有关联槽
```
QObject::disconnect(obj1, nullptr,obj2, nullptr);
obj1->disconnect(obj2);
```