主頁 > 後端開發 > Qt原始碼閱讀(一) 信號槽的連接與呼叫

Qt原始碼閱讀(一) 信號槽的連接與呼叫

2023-03-26 06:43:20 後端開發

信號槽連接

目錄
  • 信號槽連接
    • 1. 信號的連接
    • 2 槽的呼叫

信號槽的連接,其實內部本質還是一個回呼函式,主要是維護了信號發送Object的元物件里一個連接的串列,呼叫connect函式時,將槽的一系列資訊,封裝成一個Connection,在發送信號時,通過這個串列,去回呼槽函式,

1. 信號的連接

下面列舉一種信號的連接方式,來大致講解一下信號的連接程序,

//Connect a signal to a pointer to qobject member function
    // QtPrivate::FunctionPointer<Func1>::Object回傳發送信號的物件型別
    template <typename Func1, typename Func2>
    static inline QMetaObject::Connection connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal,
                                     const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot,
                                     Qt::ConnectionType type = Qt::AutoConnection)
    {
        typedef QtPrivate::FunctionPointer<Func1> SignalType;
        typedef QtPrivate::FunctionPointer<Func2> SlotType;

        Q_STATIC_ASSERT_X(QtPrivate::HasQ_OBJECT_Macro<typename SignalType::Object>::Value,
                          "No Q_OBJECT in the class with the signal");

        //compilation error if the arguments does not match.
        // 檢查信號和槽引數是否一致
        Q_STATIC_ASSERT_X(int(SignalType::ArgumentCount) >= int(SlotType::ArgumentCount),
                          "The slot requires more arguments than the signal provides.");
		// 檢查信號和槽引數是否兼容
        Q_STATIC_ASSERT_X((QtPrivate::CheckCompatibleArguments<typename SignalType::Arguments, typename SlotType::Arguments>::value),
                          "Signal and slot arguments are not compatible.");
		// 檢查信號和槽的回傳值是否兼容
		Q_STATIC_ASSERT_X((QtPrivate::AreArgumentsCompatible<typename SlotType::ReturnType, typename SignalType::ReturnType>::value),
                          "Return type of the slot is not compatible with the return type of the signal.");

        const int *types = nullptr;
		// SignalType -> QtPrivate::FunctionPointer<Func1>
		// QtPrivate::ConnectionTypes<typename SignalType::Arguments>::types() 回傳信號引數的值對應的元型別id串列
        if (type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)
            types = QtPrivate::ConnectionTypes<typename SignalType::Arguments>::types();

        return connectImpl(sender, reinterpret_cast<void **>(&signal),
                           receiver, reinterpret_cast<void **>(&slot),
                           new QtPrivate::QSlotObject<Func2, typename QtPrivate::List_Left<typename SignalType::Arguments, SlotType::ArgumentCount>::Value,
                                           typename SignalType::ReturnType>(slot),
                            type, types, &SignalType::Object::staticMetaObject);
    }

上面主要都是一些基本的信號連接的判斷,主要是:

  1. 信號和槽的引數數量
  2. 信號和槽的引數是否兼容
  3. 信號和槽的回傳值是否兼容

然后獲取信號引數所對應的元型別Id,再就到了一個信號連接的具體內部實作中

QMetaObject::Connection QObject::connectImpl(const QObject *sender, void **signal,
                                             const QObject *receiver, void **slot,
                                             QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type,
                                             const int *types, const QMetaObject *senderMetaObject)
{
    if (!signal) {
        qWarning("QObject::connect: invalid nullptr parameter");
        if (slotObj)
            slotObj->destroyIfLastRef();
        return QMetaObject::Connection();
    }

    int signal_index = -1;
    void *args[] = { &signal_index, signal };
	// 根據呼叫來判斷是否存在信號,如果當前類沒有就去父類中尋找
	// 直到找到信號或者是最基層的類
	// 找到信號的index和信號的物件
    for (; senderMetaObject && signal_index < 0; senderMetaObject = senderMetaObject->superClass()) {
        senderMetaObject->static_metacall(QMetaObject::IndexOfMethod, 0, args);
        if (signal_index >= 0 && signal_index < QMetaObjectPrivate::get(senderMetaObject)->signalCount)
            break;
    }
    if (!senderMetaObject) {
        qWarning("QObject::connect: signal not found in %s", sender->metaObject()->className());
        slotObj->destroyIfLastRef();
        return QMetaObject::Connection(nullptr);
    }
	// 信號下標
    signal_index += QMetaObjectPrivate::signalOffset(senderMetaObject);
    return QObjectPrivate::connectImpl(sender, signal_index, receiver, slot, slotObj, type, types, senderMetaObject);
}

同樣,我們對這個函式進行分析,第一個片段是對信號發送者是否為空指標的一個判斷

if (!signal) {
    qWarning("QObject::connect: invalid nullptr parameter");
    if (slotObj)
        slotObj->destroyIfLastRef();
    return QMetaObject::Connection();
}

第二個片段是去找到信號發送者(sender)的元物件型別(Meta Object)以及信號在物件信號中的位置,如果當前物件沒有該信號,就去其父類物件去找,直到找到為止,

for (; senderMetaObject && signal_index < 0; senderMetaObject = senderMetaObject->superClass()) {
    senderMetaObject->static_metacall(QMetaObject::IndexOfMethod, 0, args);
    if (signal_index >= 0 && signal_index < QMetaObjectPrivate::get(senderMetaObject)->signalCount)
        break;
}

然后就是進一步呼叫其內部實作:

QMetaObject::Connection QObjectPrivate::connectImpl(const QObject *sender, int signal_index,
                                             const QObject *receiver, void **slot,
                                             QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type,
                                             const int *types, const QMetaObject *senderMetaObject)
{
	// 發送物件、接收物件、槽函式物件、信號發送的元物件都不為空 2023-3-11
    if (!sender || !receiver || !slotObj || !senderMetaObject) {
		// 任意一個為空,報錯且清理空間,并回傳
        const char *senderString = sender ? sender->metaObject()->className()
                                          : senderMetaObject ? senderMetaObject->className()
                                          : "Unknown";
        const char *receiverString = receiver ? receiver->metaObject()->className()
                                              : "Unknown";
        qWarning("QObject::connect(%s, %s): invalid nullptr parameter", senderString, receiverString);
        if (slotObj)
            slotObj->destroyIfLastRef();
        return QMetaObject::Connection();
    }

	// 去掉const的發送和接受物件
    QObject *s = const_cast<QObject *>(sender);
    QObject *r = const_cast<QObject *>(receiver);

	// 順序鎖,按照順序依次去對mutex去上鎖
	// 這里依次對發送和接收者的信號去上鎖
    QOrderedMutexLocker locker(signalSlotLock(sender),
                               signalSlotLock(receiver));

    if (type & Qt::UniqueConnection && slot && QObjectPrivate::get(s)->connections.loadRelaxed()) {
		// ObjectPrivate::get(s) 獲取s對應的d指標
		// connections 維護了所有的信號槽連接
        QObjectPrivate::ConnectionData *connections = QObjectPrivate::get(s)->connections.loadRelaxed();
        if (connections->signalVectorCount() > signal_index) {
			// 獲取信號的連接
            const QObjectPrivate::Connection *c2 = connections->signalVector.loadRelaxed()->at(signal_index).first.loadRelaxed();

			// 回圈遍歷
            while (c2) {
				// 如果已經存在信號和槽的連接,且為uniqueConnection,則回傳
                if (c2->receiver.loadRelaxed() == receiver && c2->isSlotObject && c2->slotObj->compare(slot)) {
                    slotObj->destroyIfLastRef();
                    return QMetaObject::Connection();
                }
                c2 = c2->nextConnectionList.loadRelaxed();
            }
        }
		// 將type與UniqueConnection進行異或,去掉UniqueConnection
        type = static_cast<Qt::ConnectionType>(type ^ Qt::UniqueConnection);
    }

	// 創建一個新的連接
    std::unique_ptr<QObjectPrivate::Connection> c{new QObjectPrivate::Connection};
    c->sender = s;
    c->signal_index = signal_index;
    QThreadData *td = r->d_func()->threadData;
    td->ref();
    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;
    }

	// 將新創建的連接加到連接串列中
    QObjectPrivate::get(s)->addConnection(signal_index, c.get());
    QMetaObject::Connection ret(c.release());
    locker.unlock();

    QMetaMethod method = QMetaObjectPrivate::signal(senderMetaObject, signal_index);
    Q_ASSERT(method.isValid());
    s->connectNotify(method);

    return ret;
}

同樣第一個部分也是對一些個空值的判斷

	// 發送物件、接收物件、槽函式物件、信號發送的元物件都不為空 2023-3-11
    if (!sender || !receiver || !slotObj || !senderMetaObject) {
		// 任意一個為空,報錯且清理空間,并回傳
        const char *senderString = sender ? sender->metaObject()->className()
                                          : senderMetaObject ? senderMetaObject->className()
                                          : "Unknown";
        const char *receiverString = receiver ? receiver->metaObject()->className()
                                              : "Unknown";
        qWarning("QObject::connect(%s, %s): invalid nullptr parameter", senderString, receiverString);
        if (slotObj)
            slotObj->destroyIfLastRef();
        return QMetaObject::Connection();
    }

然后就是一個if判斷,主要是對Qt::UniqueConnection連接的一些處理,獲取當前物件的信號連接串列,并判斷當前要連接的信號和槽,之前有沒有被連接過,如果有過連接,就直接回傳,

if (type & Qt::UniqueConnection && slot && QObjectPrivate::get(s)->connections.loadRelaxed()) {
		// ObjectPrivate::get(s) 獲取s對應的d指標
		// connections 維護了所有的信號槽連接
        QObjectPrivate::ConnectionData *connections = QObjectPrivate::get(s)->connections.loadRelaxed();
        if (connections->signalVectorCount() > signal_index) {
			// 獲取信號的連接
            const QObjectPrivate::Connection *c2 = connections->signalVector.loadRelaxed()->at(signal_index).first.loadRelaxed();

			// 回圈遍歷
            while (c2) {
				// 如果已經存在信號和槽的連接,且為uniqueConnection,則回傳
                if (c2->receiver.loadRelaxed() == receiver && c2->isSlotObject && c2->slotObj->compare(slot)) {
                    slotObj->destroyIfLastRef();
                    return QMetaObject::Connection();
                }
                c2 = c2->nextConnectionList.loadRelaxed();
            }
        }
		// 將type與UniqueConnection進行異或,去掉UniqueConnection
        type = static_cast<Qt::ConnectionType>(type ^ Qt::UniqueConnection);
    }

最后才是創建一個Connection并將連接的資訊以及信號的引數設定進去,然后保存到物件的信號連接容器里,

// 創建一個新的連接
    std::unique_ptr<QObjectPrivate::Connection> c{new QObjectPrivate::Connection};
    c->sender = s;
    c->signal_index = signal_index;
    QThreadData *td = r->d_func()->threadData;
    td->ref();
    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;
    }

	// 將新創建的連接加到連接串列中
    QObjectPrivate::get(s)->addConnection(signal_index, c.get());
    QMetaObject::Connection ret(c.release());
    locker.unlock();

    QMetaMethod method = QMetaObjectPrivate::signal(senderMetaObject, signal_index);
    Q_ASSERT(method.isValid());
    s->connectNotify(method);

    return ret;

2 槽的呼叫

定義一個信號,使用moc生成moc檔案之后,我們可以看到信號函式的定義如下:

// SIGNAL 0
void MainWindow::sgnTestFor()
{
    QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}

我們發射一個信號的時候,我們會這樣寫:

emit sgnTestFor();

我們可以看關于emit的定義:

其實emit關鍵字什么都沒有做,只是標識了一下當前發射了信號,所以本質上,發射一個信號實際上就是直接呼叫了這個信號的函式,也就是呼叫了QMetaObject中的activate函式,

函式如下:

void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index,
                           void **argv)
{
    int signal_index = local_signal_index + QMetaObjectPrivate::signalOffset(m);

    if (Q_UNLIKELY(qt_signal_spy_callback_set.loadRelaxed()))
        doActivate<true>(sender, signal_index, argv);
    else
        doActivate<false>(sender, signal_index, argv);
}

上面的qt_signal_spy_callback_set暫時不清楚是什么玩意,所以我們不管,直接看具體的doActive函式

template <bool callbacks_enabled>
void doActivate(QObject *sender, int signal_index, void **argv)
{
	// 首先獲取QObject的private物件
    QObjectPrivate *sp = QObjectPrivate::get(sender);

	// 判斷信號是否阻塞
    if (sp->blockSig)
        return;

    Q_TRACE_SCOPE(QMetaObject_activate, sender, signal_index);

    if (sp->isDeclarativeSignalConnected(signal_index)
            && QAbstractDeclarativeData::signalEmitted) {
        Q_TRACE_SCOPE(QMetaObject_activate_declarative_signal, sender, signal_index);
        QAbstractDeclarativeData::signalEmitted(sp->declarativeData, sender,
                                                signal_index, argv);
    }

    const QSignalSpyCallbackSet *signal_spy_set = callbacks_enabled ? qt_signal_spy_callback_set.loadAcquire() : nullptr;

    void *empty_argv[] = { nullptr };
    if (!argv)
        argv = empty_argv;

    if (!sp->maybeSignalConnected(signal_index)) {
        // The possible declarative connection is done, and nothing else is connected
        if (callbacks_enabled && signal_spy_set->signal_begin_callback != nullptr)
            signal_spy_set->signal_begin_callback(sender, signal_index, argv);
        if (callbacks_enabled && signal_spy_set->signal_end_callback != nullptr)
            signal_spy_set->signal_end_callback(sender, signal_index);
        return;
    }

    if (callbacks_enabled && signal_spy_set->signal_begin_callback != nullptr)
        signal_spy_set->signal_begin_callback(sender, signal_index, argv);

    bool senderDeleted = false;
    {
    Q_ASSERT(sp->connections.loadAcquire());
    QObjectPrivate::ConnectionDataPointer connections(sp->connections.loadRelaxed());
    QObjectPrivate::SignalVector *signalVector = connections->signalVector.loadRelaxed();

	// 信號連接串列,因為一個信號可能連接了多個槽	
    const QObjectPrivate::ConnectionList *list;
    if (signal_index < signalVector->count())
        list = &signalVector->at(signal_index);
    else
        list = &signalVector->at(-1);

	// 判斷當前執行緒是不是信號發送者的執行緒
    Qt::HANDLE currentThreadId = QThread::currentThreadId();
    bool inSenderThread = currentThreadId == QObjectPrivate::get(sender)->threadData.loadRelaxed()->threadId.loadRelaxed();

	// 
    // We need to check against the highest connection id to ensure that signals added
    // during the signal emission are not emitted in this emission.
    uint highestConnectionId = connections->currentConnectionId.loadRelaxed();
	// 此處也就代表著,一個信號連接的多個槽函式,或者多個連接,會以連接的順序被觸發
    do {
        QObjectPrivate::Connection *c = list->first.loadRelaxed();
        if (!c)
            continue;

        do {
            QObject * const receiver = c->receiver.loadRelaxed();
            if (!receiver)
                continue;

            QThreadData *td = c->receiverThreadData.loadRelaxed();
            if (!td)
                continue;

            bool receiverInSameThread;
			// 判斷發送和接受是不是同一個執行緒
            if (inSenderThread) {
                receiverInSameThread = currentThreadId == td->threadId.loadRelaxed();
            } else {
                // need to lock before reading the threadId, because moveToThread() could interfere
                QMutexLocker lock(signalSlotLock(receiver));
                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;
#if QT_CONFIG(thread)
            } else if (c->connectionType == Qt::BlockingQueuedConnection) {
            	// 如果發送物件和接受物件在一個執行緒,使用BlockingQueuedConnection會導致死鎖
                if (receiverInSameThread) {
                    qWarning("Qt: Dead lock detected while activating a BlockingQueuedConnection: "
                    "Sender is %s(%p), receiver is %s(%p)",
                    sender->metaObject()->className(), sender,
                    receiver->metaObject()->className(), receiver);
                }
                QSemaphore semaphore;
                {
                    QBasicMutexLocker locker(signalSlotLock(sender));
                    if (!c->receiver.loadAcquire())
                        continue;
                    QMetaCallEvent *ev = c->isSlotObject ?
                        new QMetaCallEvent(c->slotObj, sender, signal_index, argv, &semaphore) :
                        new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction,
                                           sender, signal_index, argv, &semaphore);
                    QCoreApplication::postEvent(receiver, ev);
                }
				// 阻塞直至函式執行完成
                semaphore.acquire();
                continue;
#endif
            }
			// 下面是普通連接,
			// 如果不在一個執行緒,并且使用直連,那么接收者就為空
            QObjectPrivate::Sender senderData(receiverInSameThread ? receiver : nullptr, sender, signal_index);

			// 如果是槽函式物件
            if (c->isSlotObject) {
                c->slotObj->ref();

                struct Deleter {
                    void operator()(QtPrivate::QSlotObjectBase *slot) const {
                        if (slot) slot->destroyIfLastRef();
                    }
                };
                const std::unique_ptr<QtPrivate::QSlotObjectBase, Deleter> obj{c->slotObj};

                {
                    Q_TRACE_SCOPE(QMetaObject_activate_slot_functor, obj.get());
                    obj->call(receiver, argv);
                }
            } else if (c->callFunction && c->method_offset <= receiver->metaObject()->methodOffset()) {
                //we compare the vtable to make sure we are not in the destructor of the object.
                const int method_relative = c->method_relative;
                const auto callFunction = c->callFunction;
                const int methodIndex = (Q_HAS_TRACEPOINTS || callbacks_enabled) ? c->method() : 0;
                if (callbacks_enabled && signal_spy_set->slot_begin_callback != nullptr)
                    signal_spy_set->slot_begin_callback(receiver, methodIndex, argv);

                {
                    Q_TRACE_SCOPE(QMetaObject_activate_slot, receiver, methodIndex);
                    callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv);
                }

                if (callbacks_enabled && signal_spy_set->slot_end_callback != nullptr)
                    signal_spy_set->slot_end_callback(receiver, methodIndex);
            } else {
                const int method = c->method_relative + c->method_offset;

                if (callbacks_enabled && signal_spy_set->slot_begin_callback != nullptr) {
                    signal_spy_set->slot_begin_callback(receiver, method, argv);
                }

                {
                    Q_TRACE_SCOPE(QMetaObject_activate_slot, receiver, method);
                    QMetaObject::metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv);
                }

                if (callbacks_enabled && signal_spy_set->slot_end_callback != nullptr)
                    signal_spy_set->slot_end_callback(receiver, method);
            }
		// 此處while是回圈遍歷信號所連接的槽/信號
        } while ((c = c->nextConnectionList.loadRelaxed()) != nullptr && c->id <= highestConnectionId);

	// 回圈兩次
    } while (list != &signalVector->at(-1) &&
        //start over for all signals;
        ((list = &signalVector->at(-1)), true));

        if (connections->currentConnectionId.loadRelaxed() == 0)
            senderDeleted = true;
    }
    if (!senderDeleted) {
        sp->connections.loadRelaxed()->cleanOrphanedConnections(sender);

        if (callbacks_enabled && signal_spy_set->signal_end_callback != nullptr)
            signal_spy_set->signal_end_callback(sender, signal_index);
    }
}

前面的一些基本的判斷,我們就忽略,直接找到重要的地方,回圈遍歷信號所連接的部分,

  1. 當信號槽為佇列連接,我們需要將信號丟到事件回圈里,待事件回圈將該信號發送出去,

    if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
                    || (c->connectionType == Qt::QueuedConnection)) {
                    queued_activate(sender, signal_index, c, argv);
                    continue;
    #if QT_CONFIG(thread)
    } 
    
  2. 當信號槽為阻塞佇列連接(BlockingQueuedConnection)時,首先,我們需要判斷發送和接收者是不是在一個執行緒,因為如果連接型別為BlockingQueuedConnection,發送者和接收者在一個執行緒,會導致死鎖,

    else if (c->connectionType == Qt::BlockingQueuedConnection) {
                	// 如果發送物件和接受物件在一個執行緒,使用BlockingQueuedConnection會導致死鎖
                    if (receiverInSameThread) {
                        qWarning("Qt: Dead lock detected while activating a BlockingQueuedConnection: "
                        "Sender is %s(%p), receiver is %s(%p)",
                        sender->metaObject()->className(), sender,
                        receiver->metaObject()->className(), receiver);
                    }
                    QSemaphore semaphore;
                    {
                        QBasicMutexLocker locker(signalSlotLock(sender));
                        if (!c->receiver.loadAcquire())
                            continue;
                        QMetaCallEvent *ev = c->isSlotObject ?
                            new QMetaCallEvent(c->slotObj, sender, signal_index, argv, &semaphore) :
                            new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction,
                                               sender, signal_index, argv, &semaphore);
                        QCoreApplication::postEvent(receiver, ev);
                    }
    				// 阻塞直至函式執行完成
                    semaphore.acquire();
                    continue;
    #endif
    }
    

其他型別的連接如下:

  1. 信號的連接是一個槽函式物件QSlotObject,就直接呼叫call函式

    if (c->isSlotObject) {
                    c->slotObj->ref();
    
                    struct Deleter {
                        void operator()(QtPrivate::QSlotObjectBase *slot) const {
                            if (slot) slot->destroyIfLastRef();
                        }
                    };
                    const std::unique_ptr<QtPrivate::QSlotObjectBase, Deleter> obj{c->slotObj};
    
                    {
                        Q_TRACE_SCOPE(QMetaObject_activate_slot_functor, obj.get());
                        obj->call(receiver, argv);
                    }
                } 
    
  2. 如果是其他型別,就通過QMetaObject::InvokeMetaMethod來呼叫

    else if (c->callFunction && c->method_offset <= receiver->metaObject()->methodOffset()) {
                    //we compare the vtable to make sure we are not in the destructor of the object.
                    const int method_relative = c->method_relative;
                    const auto callFunction = c->callFunction;
                    const int methodIndex = (Q_HAS_TRACEPOINTS || callbacks_enabled) ? c->method() : 0;
                    if (callbacks_enabled && signal_spy_set->slot_begin_callback != nullptr)
                        signal_spy_set->slot_begin_callback(receiver, methodIndex, argv);
    
                    {
                        Q_TRACE_SCOPE(QMetaObject_activate_slot, receiver, methodIndex);
                        callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv);
                    }
    
                    if (callbacks_enabled && signal_spy_set->slot_end_callback != nullptr)
                        signal_spy_set->slot_end_callback(receiver, methodIndex);
                } else {
                    const int method = c->method_relative + c->method_offset;
    
                    if (callbacks_enabled && signal_spy_set->slot_begin_callback != nullptr) {
                        signal_spy_set->slot_begin_callback(receiver, method, argv);
                    }
    
                    {
                        Q_TRACE_SCOPE(QMetaObject_activate_slot, receiver, method);
                        QMetaObject::metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv);
                    }
    
                    if (callbacks_enabled && signal_spy_set->slot_end_callback != nullptr)
                        signal_spy_set->slot_end_callback(receiver, method);
    }
    

并且遍歷整個串列,將所有相關的連接都呼叫一遍,

然后我們看QueuedConnection的連接函式:

代碼里,揭示了一點,就是如果我們使用信號槽連接的方式,而信號的引數不是一個元型別或者沒用qRegisterMetaType來注冊型別,那么佇列連接是不行的,槽函式是不會觸發的,

static void queued_activate(QObject *sender, int signal, QObjectPrivate::Connection *c, void **argv)
{
	// 存盤元型別引數(meta-type argument)
    const int *argumentTypes = c->argumentTypes.loadRelaxed();
    if (!argumentTypes) {
		// 獲取對應的信號
        QMetaMethod m = QMetaObjectPrivate::signal(sender->metaObject(), signal);
		// 獲取信號的引數,并檢查是否所有引數均為元型別(meta-type)
        argumentTypes = queuedConnectionTypes(m.parameterTypes());
        if (!argumentTypes) // cannot queue arguments
            argumentTypes = &DIRECT_CONNECTION_ONLY;
        if (!c->argumentTypes.testAndSetOrdered(nullptr, argumentTypes)) {
            if (argumentTypes != &DIRECT_CONNECTION_ONLY)
                delete [] argumentTypes;
            argumentTypes = c->argumentTypes.loadRelaxed();
        }
    }
	// 引數不符合要求,回傳
    if (argumentTypes == &DIRECT_CONNECTION_ONLY) // cannot activate
        return;
    int nargs = 1; // include return type
    while (argumentTypes[nargs-1])
        ++nargs;

    QBasicMutexLocker locker(signalSlotLock(c->receiver.loadRelaxed()));
    if (!c->receiver.loadRelaxed()) {
        // the connection has been disconnected before we got the lock
        return;
    }
    if (c->isSlotObject)
        c->slotObj->ref();
    locker.unlock();

	// 然后通過post一個QMetaCallEvent事件到事件回圈佇列中去
    QMetaCallEvent *ev = c->isSlotObject ?
        new QMetaCallEvent(c->slotObj, sender, signal, nargs) :
        new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction, sender, signal, nargs);

    void **args = ev->args();
    int *types = ev->types();

    types[0] = 0; // return type
    args[0] = nullptr; // return value

    if (nargs > 1) {
        for (int n = 1; n < nargs; ++n)
            types[n] = argumentTypes[n-1];

        for (int n = 1; n < nargs; ++n)
            args[n] = QMetaType::create(types[n], argv[n]);
    }

    locker.relock();
    if (c->isSlotObject)
        c->slotObj->destroyIfLastRef();
    if (!c->receiver.loadRelaxed()) {
        // the connection has been disconnected while we were unlocked
        locker.unlock();
        delete ev;
        return;
    }

    QCoreApplication::postEvent(c->receiver.loadRelaxed(), ev);
}

代碼中我們可以看到,這里是通過post一個QMetaCallEvent的事件到事件回圈中,然后由事件回圈去觸發槽函式的呼叫,

好了,對于信號和槽的分析,我們暫時就先分析到這,如果有問題是我上面沒有說明的,可以在評論區給我評論,我看到了,看懂了,我就會更新這篇博客的,

謝謝觀看 ??

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/548122.html

標籤:C++

上一篇:What is static and dynamic libraries

下一篇:Teamcenter_NX集成開發:UF_UGMGR_invoke_pdm_server函式的使用

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more