Qt Signal Slot

Table of Contents

1. Qt Signal Slot

1.1. Overview

qt 底层如何使用 meta object 信息实现 signal / slot, 具体的:

  1. 如何找到 signal 和 slot 对应的 id
  2. connection 如何建立
  3. 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

优点:

  1. 没有运行时的开销
  2. 可以把 lambda 作为 slot
  3. 支持 namespace, typedef 等, 因为 namespace, typedef 的作用变为字符串难以处理

缺点:

  1. functor-based signal 只能在 c++ 内部使用, QML 无法使用

Author: [email protected]
Date: 2020-01-02 Thu 00:00
Last updated: 2020-01-03 Fri 16:53

知识共享许可协议