qt 信号多个链接槽_Qt原理窥探信号槽的实现细节
簡介
本文是《Qt進階之路》系列文章的特別篇,濤哥在這里討論Qt信號-槽的實現(xiàn)細節(jié)。
上次的文章《Qt實用技能4-認清信號槽的本質(zhì)》中介紹過,信號-槽是一種對象之間的
通信機制,是Qt在標(biāo)準(zhǔn)C++之外,使用元對象編譯器(MOC)實現(xiàn)的語法糖。
這次通過一個簡單的案例,學(xué)習(xí)一些信號-槽的實現(xiàn)細節(jié)。
貓和老鼠的故事
還是拿上次的設(shè)定來說明:Tom有個技能叫”喵”,就是發(fā)出貓叫,而正在偷吃東西的Jerry,聽見貓叫聲就會逃跑。
我們用信號-槽的方式寫出來。
//Tom.h#pragma once#include #include class Tom : public QObject
{
Q_OBJECT
public:
Tom(QObject *parent = nullptr) : QObject(parent)
{
}
void miaow()
{
qDebug() << u8"喵!" ;
emit miao();
}
signals:
void miao();
};
//Jerry.h#pragma once
#include #include class Jerry : public QObject
{
Q_OBJECT
public:
Jerry(QObject *parent = nullptr) : QObject(parent)
{
}
public slots:
void runAway()
{
qDebug() << u8"那只貓又來了,快溜!" ;
}
};
以上面的代碼為例,要使用信號-槽功能,先決條件是繼承QObject類,并在類聲明中增加Q_OBJECT宏。
之后在”signals:” 字段之后聲明一些函數(shù),這些函數(shù)就是信號。
在”public slots:” 之后聲明的函數(shù),就是槽函數(shù)。
接下來看看我們的main函數(shù):
//main.cpp#include #include "Tom.h"#include "Jerry.h"int main(int argc, char *argv[]){
QCoreApplication a(argc, argv);
Tom tom;
Jerry jerry;
QObject::connect(&tom, &Tom::miao, &jerry, &Jerry::runAway);
tom.miaow();
return a.exec();
}
信號-槽都準(zhǔn)備好了,接下來創(chuàng)建兩個對象實例,并使用QObject::connect將信號和槽連接起來。
最后使用emit發(fā)送信號,就會自動觸發(fā)槽函數(shù)了。
運行結(jié)果:
聲明與實現(xiàn)
信號和槽的本質(zhì)都是函數(shù)。
我們知道C++中的函數(shù)要有聲明(declare),也要有實現(xiàn)(implement),
而信號只要聲明,不需要寫實現(xiàn)。這是因為moc會為我們自動生成。
另外觸發(fā)信號時,不寫emit關(guān)鍵字,直接調(diào)用信號函數(shù),也是沒有問題的。
這是因為emit是一個空的宏
#define emitQ_OBJECT宏
我們來看一下Q_OBJECT宏,展開如下:
(不同的Qt版本有些差異,濤哥這里用的是5.12.4,以此為例)
public: \QT_WARNING_PUSH \
Q_OBJECT_NO_OVERRIDE_WARNING \
static const QMetaObject staticMetaObject; \
virtual const QMetaObject *metaObject() const; \
virtual void *qt_metacast(const char *); \
virtual int qt_metacall(QMetaObject::Call, int, void **); \
QT_TR_FUNCTIONS \
private: \
Q_OBJECT_NO_ATTRIBUTES_WARNING \
Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
QT_WARNING_POP \
struct QPrivateSignal {}; \
QT_ANNOTATE_CLASS(qt_qobject, "")
我們看到,關(guān)鍵的地方,是聲明了一個只讀的靜態(tài)成員變量staticMetaObject,以及3個public的成員函數(shù)
static const QMetaObject staticMetaObject;virtual const QMetaObject *metaObject() const;
virtual void *qt_metacast(const char *);
virtual int qt_metacall(QMetaObject::Call, int, void **);
還有一個private的靜態(tài)成員函數(shù)qt_static_metacall
static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **)那么聲明的這些成員變量/函數(shù),在哪里實現(xiàn)?答案是moc生成的cpp文件。
信號的moc生成
如上圖所示目錄結(jié)構(gòu),項目編譯完成后,在build文件夾中,自動生成了moc_Jerry.cpp 和 moc_Tom.cpp兩個文件
其中moc_Tom.cpp內(nèi)容如下:
/****************************************************************************** Meta object code from reading C++ file 'Tom.h'**** Created by: The Qt Meta Object Compiler version 67 (Qt 5.12.4)**** WARNING! All changes made in this file will be lost!*****************************************************************************/#include "../../TomJerry/Tom.h"#include #include #if !defined(Q_MOC_OUTPUT_REVISION)#error "The header file 'Tom.h' doesn't include ."#elif Q_MOC_OUTPUT_REVISION != 67#error "This file was generated using the moc from 5.12.4. 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_Tom_t {
QByteArrayData data[3];
char stringdata0[10];
};
#define QT_MOC_LITERAL(idx, ofs, len) \ Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
qptrdiff(offsetof(qt_meta_stringdata_Tom_t, stringdata0) + ofs \
- idx * sizeof(QByteArrayData)) \
)
static const qt_meta_stringdata_Tom_t qt_meta_stringdata_Tom = {
{
QT_MOC_LITERAL(0, 0, 3), // "Tom"QT_MOC_LITERAL(1, 4, 4), // "miao"QT_MOC_LITERAL(2, 9, 0) // ""
},
"Tom\0miao\0"
};
#undef QT_MOC_LITERAL
static const uint qt_meta_data_Tom[] = {
// content: 8, // revision 0, // classname 0, 0, // classinfo 1, 14, // methods 0, 0, // properties 0, 0, // enums/sets 0, 0, // constructors 0, // flags 1, // signalCount
// signals: name, argc, parameters, tag, flags 1, 0, 19, 2, 0x06 /* Public */,
// signals: parameters QMetaType::Void,
0 // eod};
void Tom::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
auto *_t = static_cast<Tom *>(_o);
Q_UNUSED(_t)
switch (_id) {
case 0: _t->miao(); break;
default: ;
}
} else if (_c == QMetaObject::IndexOfMethod) {
int *result = reinterpret_cast<int *>(_a[0]);
{
using _t = void (Tom::*)();
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&Tom::miao)) {
*result = 0;
return;
}
}
}
Q_UNUSED(_a);
}
QT_INIT_METAOBJECT const QMetaObject Tom::staticMetaObject = { {
&QObject::staticMetaObject,
qt_meta_stringdata_Tom.data,
qt_meta_data_Tom,
qt_static_metacall,
nullptr,
nullptr
} };
const QMetaObject *Tom::metaObject() const
{
return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}
void *Tom::qt_metacast(const char *_clname)
{
if (!_clname) return nullptr;
if (!strcmp(_clname, qt_meta_stringdata_Tom.stringdata0))
return static_cast<void*>(this);
return QObject::qt_metacast(_clname);
}
int Tom::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 < 1)
qt_static_metacall(this, _c, _id, _a);
_id -= 1;
} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
if (_id < 1)
*reinterpret_cast<int*>(_a[0]) = -1;
_id -= 1;
}
return _id;
}
// SIGNAL 0void Tom::miao()
{
QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}
QT_WARNING_POP
QT_END_MOC_NAMESPACE
可以大致看出,生成的cpp文件中,就是變量staticMetaObject以及 那幾個函數(shù)的實現(xiàn)。
staticMetaObject是一個結(jié)構(gòu)體,用來存儲Tom這個類的信號、槽等元信息,并把
qt_static_metacall靜態(tài)函數(shù)作為函數(shù)指針存儲起來。
因為是靜態(tài)成員,所以實例化多少個Tom對象,它們的元信息都是一樣的。
qt_static_metacall函數(shù)提供了兩種“元調(diào)用的實現(xiàn)”:
如果是InvokeMetaMethod類型的調(diào)用,則直接 把參數(shù)中的QObject對象,
轉(zhuǎn)換成Tom類然后調(diào)用其miao函數(shù)
如果是IndexOfMethod類型的調(diào)用,即獲取元函數(shù)的索引號,則計算miao函數(shù)的偏移并返回。
而moc_Tom.cpp末尾的
// SIGNAL 0void Tom::miao(){
QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}
就是信號函數(shù)的實現(xiàn)。
信號的觸發(fā)
miao信號的實現(xiàn),直接調(diào)用了QMetaObject::activate函數(shù)。其中0代表miao這個函數(shù)的索引號。
QMetaObject::activate函數(shù)的實現(xiàn),在Qt源碼的QObject.cpp文件中,略微復(fù)雜一些,且不同版本的Qt,實現(xiàn)差異都比較大,
這里總結(jié)一下大致的實現(xiàn):
先找出與當(dāng)前信號連接的所有對象-槽函數(shù),再逐個處理。
這里處理的方式,分為三種:
if((c->connectionType == Qt::AutoConnection && !receiverInSameThread)|| (c->connectionType == Qt::QueuedConnection)) {
// 隊列處理} else if (c->connectionType == Qt::BlockingQueuedConnection) {
// 阻塞處理 // 如果同線程,打印潛在死鎖。} else {
//直接調(diào)用槽函數(shù)或回調(diào)函數(shù)}
receiverInSameThread表示當(dāng)前線程id和接收信號的對象的所在線程id是否相等。
如果信號-槽連接方式為QueuedConnection,不論是否在同一個線程,按隊列處理。
如果信號-槽連接方式為Auto,且不在同一個線程,也按隊列處理。
如果信號-槽連接方式為阻塞隊列BlockingQueuedConnection,按阻塞處理。
(注意同一個線程就不要按阻塞隊列調(diào)用了。因為同一個線程,同時只能做一件事,本身就是阻塞的,直接調(diào)用就好了,
如果走阻塞隊列,則多了加鎖的過程。如果槽中又發(fā)了同樣的信號,就會出現(xiàn)死鎖:加鎖之后還未解鎖,又來申請加鎖。)
隊列處理,就是把槽函數(shù)的調(diào)用,轉(zhuǎn)化成了QMetaCallEvent事件,通過QCoreApplication::postEvent放進了事件循環(huán)。
等到下一次事件分發(fā),相應(yīng)的線程才會去調(diào)用槽函數(shù)。
關(guān)于事件循環(huán),可以參考之前的文章《Qt實用技能3-理解事件循環(huán)》
槽和moc生成
slot函數(shù)我們自己實現(xiàn)了,moc不會做額外的處理,所以自動生成的moc_Jerry.cpp文件中,只有Q_OBJECT宏的展開,
和前面的moc_Tom.cpp是一致的,不贅述了。
第三方信號槽實現(xiàn)
信號-槽是非常優(yōu)秀的通信機制,但Qt的moc實現(xiàn)方式,被一些人詬病,所以他們造了新的輪子,比如:
woboq.com/blog/verdigri
sigslot.sourceforge.net
github.com/NoAvailableA
github.com/pbhogan/Sign
相關(guān)鏈接
濤哥的博客:https://jaredtao.github.io
武威濤哥的博客jaredtao.github.io
濤哥的博客-國內(nèi)鏡像:?https://jaredtao.gitee.io
https://jaredtao.gitee.iojaredtao.gitee.io
高質(zhì)量交流 QQ群:734623697?
大佬多,不灌水
總結(jié)
以上是生活随笔為你收集整理的qt 信号多个链接槽_Qt原理窥探信号槽的实现细节的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: access创建窗体特别慢_64位Acc
- 下一篇: redis 判断存在性_实战 | spr