手撕Qt信號(hào)槽原理

本文使用Qt 5.12.6 + MinGW7.3.0.64+win10環(huán)境+qtbase源碼

我們來看一下以下幾個(gè)問題绽昏,如果你大腦里面都有清晰的答案,請(qǐng)出門右轉(zhuǎn)

  1. 什么moc( Meta-Object Compiler)預(yù)編譯
  2. 為什么要有signals和slots關(guān)鍵字
  3. 信號(hào)槽連接有哪幾種類型
  4. 信號(hào)和槽函數(shù)有什么區(qū)別
  5. connect到底干了什么
  6. 信號(hào)觸發(fā)的原理

一.Meta-Object Compiler

新建一個(gè)qml工程,打開main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

我們可以看到main函數(shù)中默認(rèn)生成了一個(gè)信號(hào)槽

    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);

這是一種c++11新特性的寫法盘寡,我們可以先略過。我們先看一下qobject.h中connect函數(shù)的其中一種定義

    static QMetaObject::Connection connect(const QObject *sender, const QMetaMethod &signal,
                        const QObject *receiver, const QMetaMethod &method,
                        Qt::ConnectionType type = Qt::AutoConnection);

總共五個(gè)參數(shù),sender和receiver都是QObject扼脐,signal和method都是QMetaMethod,type是Qt::ConnectionType奋刽,我們結(jié)合main函數(shù)中的connect范例來分別看看這幾個(gè)參數(shù)瓦侮。
sender對(duì)應(yīng)范例中的engine,是一個(gè)QQmlApplicationEngine 對(duì)象佣谐,打開QQmlApplicationEngine 源碼重點(diǎn)關(guān)注一下標(biāo)紅的部分


qt信號(hào)槽截圖1.png

1.public QQmlEngine表明了繼承關(guān)系(QQmlEngine : public QJSEngine: public QObject)
2.Q_OBJECT是一個(gè)非常重要的宏肚吏,他是Qt實(shí)現(xiàn)元編譯系統(tǒng)的一個(gè)關(guān)鍵宏,這個(gè)宏展開后狭魂,里邊包含了很多Qt幫我們寫的代碼罚攀,包括了變量定義党觅、函數(shù)聲明等等。為了方便沒有下載Qt源碼的同學(xué)斋泄,我們?cè)趧偛诺睦又行录尤胍粋€(gè)沒有父類的類ZConnection杯瞻,然后執(zhí)行qmake和make,編譯通過后打開我們的工程目錄炫掐,如果沒有做特殊配置的話工程目錄的同級(jí)目錄會(huì)生成一個(gè)類似下面的文件夾


qt信號(hào)槽截圖3.png

然后我們對(duì)剛才的類進(jìn)行改造又兵,讓它繼承自QObject,添加Q_OBJECT卒废,并添加一個(gè)信號(hào)和一個(gè)槽函數(shù)沛厨,類似下面的樣子
.h文件如下

#ifndef ZCONNECTION_H
#define ZCONNECTION_H

#include <QObject>

class ZConnection : public QObject
{
    Q_OBJECT
public:
    explicit ZConnection(QObject *parent = nullptr);

public slots:
    void testSlot();

signals:
    void testSignal();
};

#endif // ZCONNECTION_H

.cpp文件如下

#include "zconnection.h"
#include <QDebug>

ZConnection::ZConnection(QObject *parent) : QObject(parent)
{
    connect(this, &ZConnection::testSignal, this, &ZConnection::testSlot);
}

void ZConnection::testSlot()
{
    qDebug() << Q_FUNC_INFO;
}

然后清空debug文件夾,執(zhí)行qmake和make摔认,然后再觀察debug目錄下的文件

image.png

我們會(huì)發(fā)現(xiàn)多了幾個(gè)moc_開頭的文件逆皮,我們打開moc_zconnection.cpp文件:

/****************************************************************************
** Meta object code from reading C++ file 'zconnection.h'
**
** Created by: The Qt Meta Object Compiler version 67 (Qt 5.12.6)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/

#include "../../testConnection/zconnection.h"
#include <QtCore/qbytearray.h>
#include <QtCore/qmetatype.h>
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'zconnection.h' doesn't include <QObject>."
#elif Q_MOC_OUTPUT_REVISION != 67
#error "This file was generated using the moc from 5.12.6. It"
#error "cannot be used with the include files from this version of Qt."
#error "(The moc has changed too much.)"
#endif

QT_BEGIN_MOC_NAMESPACE
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
struct qt_meta_stringdata_ZConnection_t {
    QByteArrayData data[4];
    char stringdata0[33];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
    Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
    qptrdiff(offsetof(qt_meta_stringdata_ZConnection_t, stringdata0) + ofs \
        - idx * sizeof(QByteArrayData)) \
    )
static const qt_meta_stringdata_ZConnection_t qt_meta_stringdata_ZConnection = {
    {
QT_MOC_LITERAL(0, 0, 11), // "ZConnection"
QT_MOC_LITERAL(1, 12, 10), // "testSignal"
QT_MOC_LITERAL(2, 23, 0), // ""
QT_MOC_LITERAL(3, 24, 8) // "testSlot"

    },
    "ZConnection\0testSignal\0\0testSlot"
};
#undef QT_MOC_LITERAL

static const uint qt_meta_data_ZConnection[] = {

 // content:
       8,       // revision
       0,       // classname
       0,    0, // classinfo
       2,   14, // methods
       0,    0, // properties
       0,    0, // enums/sets
       0,    0, // constructors
       0,       // flags
       1,       // signalCount

 // signals: name, argc, parameters, tag, flags
       1,    0,   24,    2, 0x06 /* Public */,

 // slots: name, argc, parameters, tag, flags
       3,    0,   25,    2, 0x0a /* Public */,

 // signals: parameters
    QMetaType::Void,

 // slots: parameters
    QMetaType::Void,

       0        // eod
};

void ZConnection::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        auto *_t = static_cast<ZConnection *>(_o);
        Q_UNUSED(_t)
        switch (_id) {
        case 0: _t->testSignal(); break;
        case 1: _t->testSlot(); break;
        default: ;
        }
    } else if (_c == QMetaObject::IndexOfMethod) {
        int *result = reinterpret_cast<int *>(_a[0]);
        {
            using _t = void (ZConnection::*)();
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&ZConnection::testSignal)) {
                *result = 0;
                return;
            }
        }
    }
    Q_UNUSED(_a);
}

QT_INIT_METAOBJECT const QMetaObject ZConnection::staticMetaObject = { {
    &QObject::staticMetaObject,
    qt_meta_stringdata_ZConnection.data,
    qt_meta_data_ZConnection,
    qt_static_metacall,
    nullptr,
    nullptr
} };


const QMetaObject *ZConnection::metaObject() const
{
    return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}

void *ZConnection::qt_metacast(const char *_clname)
{
    if (!_clname) return nullptr;
    if (!strcmp(_clname, qt_meta_stringdata_ZConnection.stringdata0))
        return static_cast<void*>(this);
    return QObject::qt_metacast(_clname);
}

int ZConnection::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
    _id = QObject::qt_metacall(_c, _id, _a);
    if (_id < 0)
        return _id;
    if (_c == QMetaObject::InvokeMetaMethod) {
        if (_id < 2)
            qt_static_metacall(this, _c, _id, _a);
        _id -= 2;
    } else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
        if (_id < 2)
            *reinterpret_cast<int*>(_a[0]) = -1;
        _id -= 2;
    }
    return _id;
}

// SIGNAL 0
void ZConnection::testSignal()
{
    QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}
QT_WARNING_POP
QT_END_MOC_NAMESPACE

我們來看看這個(gè)moc文件中的幾個(gè)函數(shù)

void ZConnection::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
QT_INIT_METAOBJECT const QMetaObject ZConnection::staticMetaObject
const QMetaObject *ZConnection::metaObject() const
void *ZConnection::qt_metacast(const char *_clname)
int ZConnection::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
void ZConnection::testSignal()
  1. signal
    我們可以看到moc幫我們?cè)谶@里添加了testSignal的實(shí)現(xiàn),由此可見参袱,信號(hào)其實(shí)也是一個(gè)函數(shù)电谣,只是我們只管寫信號(hào)聲明,而信號(hào)實(shí)現(xiàn)moc會(huì)幫助我們自動(dòng)生成抹蚀,信號(hào)觸發(fā)后實(shí)際調(diào)用的是QMetaObject::activate剿牺,activate函數(shù)具體又做了什么,我們?cè)诤竺嬖僬归_來講环壤。槽函數(shù)我們不僅僅需要寫函數(shù)聲明晒来,函數(shù)實(shí)現(xiàn)也必須自己寫。
void ZConnection::testSignal()
{
    QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}

這里Qt怎么會(huì)知道我們定義了信號(hào)呢郑现?答案就是【signals】關(guān)鍵字湃崩,當(dāng)moc發(fā)現(xiàn)這個(gè)標(biāo)志后,默認(rèn)我們是在定義信號(hào)接箫,它則幫助我們生產(chǎn)了信號(hào)的實(shí)現(xiàn)體攒读,【slots】關(guān)鍵字也是同樣的道理,moc用來解析槽函數(shù)時(shí)用的辛友。

  1. qt_static_metacall
    根據(jù)函數(shù)索引調(diào)用槽函數(shù)薄扁,需要注意一個(gè)細(xì)節(jié)【這個(gè)回調(diào)中信號(hào)和槽都是可以被回調(diào)的】,自動(dòng)生成代碼如下
void ZConnection::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        auto *_t = static_cast<ZConnection *>(_o);
        Q_UNUSED(_t)
        switch (_id) {
        case 0: _t->testSignal(); break;
        case 1: _t->testSlot(); break;
        default: ;
        }
    } else if (_c == QMetaObject::IndexOfMethod) {
        int *result = reinterpret_cast<int *>(_a[0]);
        {
            using _t = void (ZConnection::*)();
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&ZConnection::testSignal)) {
                *result = 0;
                return;
            }
        }
    }
    Q_UNUSED(_a);
}

testSignal是一個(gè)信號(hào)聲明废累,但是卻也可以被回調(diào)邓梅,這也間接的說明了一個(gè)問題,信號(hào)是可以當(dāng)槽函數(shù)一樣使用的九默。

  1. staticMetaObject
    構(gòu)造一個(gè)QMetaObject對(duì)象震放,傳入當(dāng)前moc文件的動(dòng)態(tài)信息
  2. metaObject
    返回當(dāng)前QMetaObject,一般而言驼修,虛函數(shù) metaObject() 僅返回類的 staticMetaObject對(duì)象殿遂。
  3. qt_metacast
    是否可以進(jìn)行類型轉(zhuǎn)換诈铛,被QObject::inherits直接調(diào)用,用于判斷是否是繼承自某個(gè)類墨礁。判斷時(shí)幢竹,需要傳入父類的字符串名稱。
  4. qt_metacall
    調(diào)用函數(shù)回調(diào)恩静,內(nèi)部還是調(diào)用了qt_static_metacall函數(shù)焕毫,該函數(shù)被異步處理信號(hào)時(shí)調(diào)用,或者Qt規(guī)定的有一定格式的槽函數(shù)(on_xxx_clicked())觸發(fā)

二.Connect

上面我們分析了moc幫助我們生成的moc文件驶乾,他是實(shí)現(xiàn)信號(hào)槽的基礎(chǔ)邑飒,現(xiàn)在我們來了解下connect函數(shù),看看他到底干了些什么级乐。

先來看看connect的函數(shù)實(shí)現(xiàn)疙咸,我這里把涉及到的三個(gè)主要函數(shù)的實(shí)現(xiàn)都貼出來了,方便大家查看

QMetaObject::Connection QObject::connect(const QObject *sender, const QMetaMethod &signal,
                                     const QObject *receiver, const QMetaMethod &method,
                                     Qt::ConnectionType type)
{
    if (sender == 0
            || receiver == 0
            || signal.methodType() != QMetaMethod::Signal
            || method.methodType() == QMetaMethod::Constructor) {
        qWarning("QObject::connect: Cannot connect %s::%s to %s::%s",
                 sender ? sender->metaObject()->className() : "(null)",
                 signal.methodSignature().constData(),
                 receiver ? receiver->metaObject()->className() : "(null)",
                 method.methodSignature().constData() );
        return QMetaObject::Connection(0);
    }

    int signal_index;
    int method_index;
    {
        int dummy;
        QMetaObjectPrivate::memberIndexes(sender, signal, &signal_index, &dummy);
        QMetaObjectPrivate::memberIndexes(receiver, method, &dummy, &method_index);
    }

    const QMetaObject *smeta = sender->metaObject();
    const QMetaObject *rmeta = receiver->metaObject();
    if (signal_index == -1) {
        qWarning("QObject::connect: Can't find signal %s on instance of class %s",
                 signal.methodSignature().constData(), smeta->className());
        return QMetaObject::Connection(0);
    }
    if (method_index == -1) {
        qWarning("QObject::connect: Can't find method %s on instance of class %s",
                 method.methodSignature().constData(), rmeta->className());
        return QMetaObject::Connection(0);
    }

    if (!QMetaObject::checkConnectArgs(signal.methodSignature().constData(), method.methodSignature().constData())) {
        qWarning("QObject::connect: Incompatible sender/receiver arguments"
                 "\n        %s::%s --> %s::%s",
                 smeta->className(), signal.methodSignature().constData(),
                 rmeta->className(), method.methodSignature().constData());
        return QMetaObject::Connection(0);
    }

    int *types = 0;
    if ((type == Qt::QueuedConnection)
            && !(types = queuedConnectionTypes(signal.parameterTypes())))
        return QMetaObject::Connection(0);

#ifndef QT_NO_DEBUG
    check_and_warn_compat(smeta, signal, rmeta, method);
#endif
    QMetaObject::Connection handle = QMetaObject::Connection(QMetaObjectPrivate::connect(
        sender, signal_index, signal.enclosingMetaObject(), receiver, method_index, 0, type, types));
    return handle;
}
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)
{
    QObject *s = const_cast<QObject *>(sender);
    QObject *r = const_cast<QObject *>(receiver);

    int method_offset = rmeta ? rmeta->methodOffset() : 0;
    Q_ASSERT(!rmeta || QMetaObjectPrivate::get(rmeta)->revision >= 6);
    QObjectPrivate::StaticMetaCallFunction callFunction =
        rmeta ? rmeta->d.static_metacall : 0;

    QOrderedMutexLocker locker(signalSlotLock(sender),
                               signalSlotLock(receiver));

    if (type & Qt::UniqueConnection) {
        QObjectConnectionListVector *connectionLists = QObjectPrivate::get(s)->connectionLists;
        if (connectionLists && connectionLists->count() > signal_index) {
            const QObjectPrivate::Connection *c2 =
                (*connectionLists)[signal_index].first;

            int method_index_absolute = method_index + method_offset;

            while (c2) {
                if (!c2->isSlotObject && c2->receiver == receiver && c2->method() == method_index_absolute)
                    return 0;
                c2 = c2->nextConnectionList;
            }
        }
        type &= Qt::UniqueConnection - 1;
    }

    QScopedPointer<QObjectPrivate::Connection> c(new QObjectPrivate::Connection);
    c->sender = s;
    c->signal_index = signal_index;
    c->receiver = r;
    c->method_relative = method_index;
    c->method_offset = method_offset;
    c->connectionType = type;
    c->isSlotObject = false;
    c->argumentTypes.store(types);
    c->nextConnectionList = 0;
    c->callFunction = callFunction;

    QObjectPrivate::get(s)->addConnection(signal_index, c.data());

    locker.unlock();
    QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index);
    if (smethod.isValid())
        s->connectNotify(smethod);

    return c.take();
}
void QObjectPrivate::addConnection(int signal, Connection *c)
{
    Q_ASSERT(c->sender == q_ptr);
    if (!connectionLists)
        connectionLists = new QObjectConnectionListVector();
    if (signal >= connectionLists->count())
        connectionLists->resize(signal + 1);

    ConnectionList &connectionList = (*connectionLists)[signal];
    if (connectionList.last) {
        connectionList.last->nextConnectionList = c;
    } else {
        connectionList.first = c;
    }
    connectionList.last = c;

    cleanConnectionLists();

    c->prev = &(QObjectPrivate::get(c->receiver)->senders);
    c->next = *c->prev;
    *c->prev = c;
    if (c->next)
        c->next->prev = &c->next;

    if (signal < 0) {
        connectedSignals[0] = connectedSignals[1] = ~0;
    } else if (signal < (int)sizeof(connectedSignals) * 8) {
        connectedSignals[signal >> 5] |= (1 << (signal & 0x1f));
    }
}

大致流程如下


image.png

信號(hào)槽連接后在內(nèi)存中以QObjectConnectionListVector對(duì)象存儲(chǔ)风科,這是一個(gè)數(shù)組撒轮,Qt巧妙的借用了數(shù)組快速訪問指定元素的方式,把信號(hào)所在的索引作為下標(biāo)來索引他連接的Connection對(duì)象贼穆,眾所周知一個(gè)信號(hào)可以被多個(gè)槽連接题山,那么我們的的數(shù)組自然而然也就存儲(chǔ)了一個(gè)鏈表,用于方便的插入和移除故痊,也就是ConnectionList對(duì)象顶瞳。

三、signal&slot

首先來看一下ConnectionType

    enum ConnectionType {
        AutoConnection,
        DirectConnection,
        QueuedConnection,
        BlockingQueuedConnection,
        UniqueConnection =  0x80
    };
  • Qt::AutoConnection 自動(dòng)連接崖蜜,根據(jù)sender和receiver是否在一個(gè)線程里來決定使用哪種連接方式浊仆,同一個(gè)線程使用直連,否則使用隊(duì)列連接
  • Qt::DirectConnection 直連
  • Qt::QueuedConnection 隊(duì)列連接
  • Qt::BlockingQueuedConnection 阻塞隊(duì)列連接豫领,顧名思義,雖然是跨線程的舔琅,但是還是希望槽執(zhí)行完之后等恐,才能執(zhí)行信號(hào)的下一步代碼
  • Qt::UniqueConnection 唯一連接
    前邊我們已經(jīng)提到信號(hào)觸發(fā)后實(shí)際調(diào)用的QMetaObject::activate函數(shù),大致流程如下圖所示
image.png

QMetaObject::activate函數(shù)源碼如下:

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

    if (sender->d_func()->blockSig)
        return;

    Q_TRACE_SCOPE(QMetaObject_activate, sender, signal_index);

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

    if (!sender->d_func()->isSignalConnected(signal_index, /*checkDeclarative =*/ false)
        && !qt_signal_spy_callback_set.signal_begin_callback
        && !qt_signal_spy_callback_set.signal_end_callback) {
        // The possible declarative connection is done, and nothing else is connected, so:
        return;
    }

    void *empty_argv[] = { 0 };
    if (qt_signal_spy_callback_set.signal_begin_callback != 0) {
        qt_signal_spy_callback_set.signal_begin_callback(sender, signal_index,
                                                         argv ? argv : empty_argv);
    }

    {
    QMutexLocker locker(signalSlotLock(sender));
    struct ConnectionListsRef {
        QObjectConnectionListVector *connectionLists;
        ConnectionListsRef(QObjectConnectionListVector *connectionLists) : connectionLists(connectionLists)
        {
            if (connectionLists)
                ++connectionLists->inUse;
        }
        ~ConnectionListsRef()
        {
            if (!connectionLists)
                return;

            --connectionLists->inUse;
            Q_ASSERT(connectionLists->inUse >= 0);
            if (connectionLists->orphaned) {
                if (!connectionLists->inUse)
                    delete connectionLists;
            }
        }

        QObjectConnectionListVector *operator->() const { return connectionLists; }
    };
    ConnectionListsRef connectionLists = sender->d_func()->connectionLists;
    if (!connectionLists.connectionLists) {
        locker.unlock();
        if (qt_signal_spy_callback_set.signal_end_callback != 0)
            qt_signal_spy_callback_set.signal_end_callback(sender, signal_index);
        return;
    }

    // 數(shù)組里面取鏈表
    const QObjectPrivate::ConnectionList *list;
    if (signal_index < connectionLists->count())
        list = &connectionLists->at(signal_index);
    else
        list = &connectionLists->allsignals;

    Qt::HANDLE currentThreadId = QThread::currentThreadId();

    do {
        QObjectPrivate::Connection *c = list->first;
        if (!c) continue;
        // We need to check against last here to ensure that signals added
        // during the signal emission are not emitted in this emission.
        QObjectPrivate::Connection *last = list->last;

        do {
            if (!c->receiver)
                continue;

            QObject * const receiver = c->receiver;
            const bool receiverInSameThread = currentThreadId == receiver->d_func()->threadData->threadId.load();

            // 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 ? argv : empty_argv, locker);
                continue;
#if QT_CONFIG(thread)
            }
            // 阻塞
            else if (c->connectionType == Qt::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);
                }
                // 信號(hào)量處理
                QSemaphore semaphore;
                QMetaCallEvent *ev = c->isSlotObject ?
                    new QMetaCallEvent(c->slotObj, sender, signal_index, 0, 0, argv ? argv : empty_argv, &semaphore) :
                    new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction, sender, signal_index, 0, 0, argv ? argv : empty_argv, &semaphore);
                QCoreApplication::postEvent(receiver, ev);
                locker.unlock();
                semaphore.acquire();
                locker.relock();
                continue;
#endif
            }

            QConnectionSenderSwitcher sw;

            if (receiverInSameThread) {
                sw.switchSender(receiver, sender, signal_index);
            }
            if (c->isSlotObject) {
                c->slotObj->ref();
                QScopedPointer<QtPrivate::QSlotObjectBase, QSlotObjectBaseDeleter> obj(c->slotObj);
                locker.unlock();

                {
                    Q_TRACE_SCOPE(QMetaObject_activate_slot_functor, obj.data());
                    obj->call(receiver, argv ? argv : empty_argv);
                }

                // Make sure the slot object gets destroyed before the mutex is locked again, as the
                // destructor of the slot object might also lock a mutex from the signalSlotLock() mutex pool,
                // and that would deadlock if the pool happens to return the same mutex.
                obj.reset();

                locker.relock();
            } 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 methodIndex = c->method();
                const int method_relative = c->method_relative;
                const auto callFunction = c->callFunction;
                locker.unlock();
                if (qt_signal_spy_callback_set.slot_begin_callback != 0)
                    qt_signal_spy_callback_set.slot_begin_callback(receiver, methodIndex, argv ? argv : empty_argv);

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

                if (qt_signal_spy_callback_set.slot_end_callback != 0)
                    qt_signal_spy_callback_set.slot_end_callback(receiver, methodIndex);
                locker.relock();
            } else {
                const int method = c->method_relative + c->method_offset;
                locker.unlock();

                if (qt_signal_spy_callback_set.slot_begin_callback != 0) {
                    qt_signal_spy_callback_set.slot_begin_callback(receiver,
                                                                method,
                                                                argv ? argv : empty_argv);
                }

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

                if (qt_signal_spy_callback_set.slot_end_callback != 0)
                    qt_signal_spy_callback_set.slot_end_callback(receiver, method);

                locker.relock();
            }

            if (connectionLists->orphaned)
                break;
            // 一個(gè)信號(hào)對(duì)應(yīng)多個(gè)槽函數(shù)
        } while (c != last && (c = c->nextConnectionList) != 0);

        if (connectionLists->orphaned)
            break;
    } while (list != &connectionLists->allsignals &&
        //start over for all signals;
        ((list = &connectionLists->allsignals), true));

    }

    if (qt_signal_spy_callback_set.signal_end_callback != 0)
        qt_signal_spy_callback_set.signal_end_callback(sender, signal_index);
}

四备蚓、總結(jié)

Qt信號(hào)槽的實(shí)現(xiàn)原理其實(shí)就是函數(shù)回調(diào)课蔬,不同的是直連直接回調(diào)、隊(duì)列連接使用Qt的事件循環(huán)隔離了一次達(dá)到異步郊尝,最終還是使用函數(shù)回調(diào)

  • moc預(yù)編譯幫助我們構(gòu)建了信號(hào)槽回調(diào)的開頭(信號(hào)函數(shù)體)和結(jié)尾(qt_static_metacall回調(diào)函數(shù))二跋,中間的回調(diào)過程Qt已經(jīng)在QOjbect函數(shù)中實(shí)現(xiàn)
  • signals和slots就是為了方便moc解析我們的C++文件,從中解析出信號(hào)和槽
  • 信號(hào)槽總共有5種連接方式流昏,前四種是互斥的扎即,可以表示為異步和同步吞获。第五種唯一連接時(shí)配合前4種方式使用的
  • 信號(hào)和槽本質(zhì)上是一樣的,但是對(duì)于使用者來說谚鄙,信號(hào)只需要聲明各拷,moc幫你實(shí)現(xiàn),槽函數(shù)聲明和實(shí)現(xiàn)都需要自己寫
  • connect方法就是把發(fā)送者闷营、信號(hào)烤黍、接受者和槽存儲(chǔ)起來,供后續(xù)執(zhí)行信號(hào)時(shí)查找
  • 信號(hào)觸發(fā)就是一系列函數(shù)回調(diào)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末傻盟,一起剝皮案震驚了整個(gè)濱河市速蕊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌娘赴,老刑警劉巖互例,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異筝闹,居然都是意外死亡媳叨,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門关顷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來糊秆,“玉大人,你說我怎么就攤上這事议双《环” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵平痰,是天一觀的道長汞舱。 經(jīng)常有香客問我,道長宗雇,這世上最難降的妖魔是什么昂芜? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮赔蒲,結(jié)果婚禮上泌神,老公的妹妹穿的比我還像新娘。我一直安慰自己舞虱,他們只是感情好欢际,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著矾兜,像睡著了一般损趋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上椅寺,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天浑槽,我揣著相機(jī)與錄音蒋失,去河邊找鬼。 笑死括荡,一個(gè)胖子當(dāng)著我的面吹牛高镐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播畸冲,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼嫉髓,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了邑闲?” 一聲冷哼從身側(cè)響起算行,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎苫耸,沒想到半個(gè)月后州邢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡褪子,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年量淌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嫌褪。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡呀枢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出笼痛,到底是詐尸還是另有隱情裙秋,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布缨伊,位于F島的核電站摘刑,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏刻坊。R本人自食惡果不足惜枷恕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望紧唱。 院中可真熱鬧活尊,春花似錦、人聲如沸漏益。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绰疤。三九已至,卻和暖如春舞终,著一層夾襖步出監(jiān)牢的瞬間轻庆,已是汗流浹背癣猾。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留余爆,地道東北人纷宇。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像蛾方,于是被迫代替她去往敵國和親像捶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容