Qt Signal Slot
Table of Contents
1. Qt Signal Slot
1.1. Overview
qt 底层如何使用 meta object 信息实现 signal / slot, 具体的:
- 如何找到 signal 和 slot 对应的 id
- connection 如何建立
- signal 如何 dispatch 到对应的 slot
1.2. 找到 id
QMetaObject::Connection QObject::connect( const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type): // signal, method 的类型为 char *, 是因为 SIGNAL 和 SLOT 宏实际上为 // # define SLOT(a) "1"#a // # define SIGNAL(a) "2"#a // 它们把要调用的 signal slot 变成为字符串 // 找到 signal index int signal_index = QMetaObjectPrivate::indexOfSignalRelative( &smeta, signalName, signalTypes.size(), signalTypes.constData()); for (const QMetaObject *m = *baseObject; m; m = m->d.superdata) : int i = (MethodType == MethodSignal) ? (priv(m->d.data)->signalCount - 1) : (priv(m->d.data)->methodCount - 1); const int end = (MethodType == MethodSlot) ? (priv(m->d.data)->signalCount) : 0; // 使用 metadata 进行了某种字符串的匹配 for (; i >= end; --i) : int handle = priv(m->d.data)->methodData + 5*i; if (methodMatch(m, handle, name, argc, types)): *baseObject = m; return i; return -1; // 找到 slot index method_index_relative = QMetaObjectPrivate::indexOfSlotRelative( &rmeta, methodName, methodTypes.size(), methodTypes.constData()); // connect, 实际上是把 slot index 加到 signal index 对应的链表上 QMetaObjectPrivate::connect( sender, signal_index, smeta, receiver, method_index_relative, rmeta ,type, types)
1.3. connect
QObjectPrivate::Connection *QMetaObjectPrivate::connect(const QObject *sender, int signal_index, const QMetaObject *smeta, const QObject *receiver, int method_index, const QMetaObject *rmeta, int type, int *types) std::unique_ptr<QObjectPrivate::Connection> c{new QObjectPrivate::Connection}; c->sender = s; c->signal_index = signal_index; c->receiver.storeRelaxed(r); c->method_relative = method_index; c->method_offset = method_offset; QObjectPrivate::get(s)->addConnection(signal_index, c.get()); ConnectionList &connectionList = cd->connectionsForSignal(signal); return signalVector.loadRelaxed()->at(signal); // ... 操作链表 connectionList.first.storeRelaxed(c); c->prevConnectionList = connectionList.last.loadRelaxed(); connectionList.last.storeRelaxed(c); c->next = *c->prev; *c->prev = c; // ...
1.4. activate
void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index,void **argv) doActivate(sender, signal_index, argv); list = &signalVector->at(signal_index); // 遍历所有的 slot, 根据 connection type 和 receiver 是否和 sender 在相同的 // thread, 决定是直接调用, postEvent, 或者 postEvent 并阻塞 do : QObjectPrivate::Connection *c = list->first.loadRelaxed(); do : QObject * const receiver = c->receiver.loadRelaxed(); bool receiverInSameThread; receiverInSameThread = currentThreadId == td->threadId.loadRelaxed(); // determine if this connection should be sent immediately or // put into the event queue if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread) || (c->connectionType == Qt::QueuedConnection)): queued_activate(sender, signal_index, c, argv); continue; else if (c->connectionType == Qt::BlockingQueuedConnection): if (receiverInSameThread): QSemaphore semaphore; { QCoreApplication::postEvent(receiver, ev); } semaphore.acquire(); else: const int method = c->method_relative + c->method_offset; QMetaObject::metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv); while ((c = c->nextConnectionList.loadRelaxed()) != nullptr && c->id <= highestConnectionId);
1.5. functor-based signal
https://doc.qt.io/qt-5/signalsandslots-syntaxes.html#
旧的 signal 系统称为 string-based, 因为它需要把 signal, slot 用 SIGNAL/SLOT 宏转换为字符串, 并通过 introspection 在运行时把字符串转换为 index
新的 functor-based signal 提供的是 signal, slot 的函数指针, 在运行时通过 static_cast 就能获得 index
优点:
- 没有运行时的开销
- 可以把 lambda 作为 slot
- 支持 namespace, typedef 等, 因为 namespace, typedef 的作用变为字符串难以处理
缺点:
- functor-based signal 只能在 c++ 内部使用, QML 无法使用