日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

qt 信号多个链接槽_Qt原理窥探信号槽的实现细节

發布時間:2024/9/15 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 qt 信号多个链接槽_Qt原理窥探信号槽的实现细节 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

簡介

本文是《Qt進階之路》系列文章的特別篇,濤哥在這里討論Qt信號-槽的實現細節。

上次的文章《Qt實用技能4-認清信號槽的本質》中介紹過,信號-槽是一種對象之間的

通信機制,是Qt在標準C++之外,使用元對象編譯器(MOC)實現的語法糖。

這次通過一個簡單的案例,學習一些信號-槽的實現細節。

貓和老鼠的故事

還是拿上次的設定來說明:Tom有個技能叫”喵”,就是發出貓叫,而正在偷吃東西的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:” 字段之后聲明一些函數,這些函數就是信號。

在”public slots:” 之后聲明的函數,就是槽函數。

接下來看看我們的main函數:

//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();
}

信號-槽都準備好了,接下來創建兩個對象實例,并使用QObject::connect將信號和槽連接起來。

最后使用emit發送信號,就會自動觸發槽函數了。

運行結果:

聲明與實現

信號和槽的本質都是函數。

我們知道C++中的函數要有聲明(declare),也要有實現(implement),

而信號只要聲明,不需要寫實現。這是因為moc會為我們自動生成。

另外觸發信號時,不寫emit關鍵字,直接調用信號函數,也是沒有問題的。

這是因為emit是一個空的宏

#define emit

Q_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, "")

我們看到,關鍵的地方,是聲明了一個只讀的靜態成員變量staticMetaObject,以及3個public的成員函數

static const QMetaObject staticMetaObject;

virtual const QMetaObject *metaObject() const;

virtual void *qt_metacast(const char *);

virtual int qt_metacall(QMetaObject::Call, int, void **);

還有一個private的靜態成員函數qt_static_metacall

static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **)

那么聲明的這些成員變量/函數,在哪里實現?答案是moc生成的cpp文件。

信號的moc生成

如上圖所示目錄結構,項目編譯完成后,在build文件夾中,自動生成了moc_Jerry.cpp 和 moc_Tom.cpp兩個文件

其中moc_Tom.cpp內容如下:

/****************************************************************************** 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以及 那幾個函數的實現。

staticMetaObject是一個結構體,用來存儲Tom這個類的信號、槽等元信息,并把

qt_static_metacall靜態函數作為函數指針存儲起來。

因為是靜態成員,所以實例化多少個Tom對象,它們的元信息都是一樣的。

qt_static_metacall函數提供了兩種“元調用的實現”:

如果是InvokeMetaMethod類型的調用,則直接 把參數中的QObject對象,

轉換成Tom類然后調用其miao函數

如果是IndexOfMethod類型的調用,即獲取元函數的索引號,則計算miao函數的偏移并返回。

而moc_Tom.cpp末尾的

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

就是信號函數的實現。

信號的觸發

miao信號的實現,直接調用了QMetaObject::activate函數。其中0代表miao這個函數的索引號。

QMetaObject::activate函數的實現,在Qt源碼的QObject.cpp文件中,略微復雜一些,且不同版本的Qt,實現差異都比較大,

這里總結一下大致的實現:

先找出與當前信號連接的所有對象-槽函數,再逐個處理。

這里處理的方式,分為三種:

if((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
|| (c->connectionType == Qt::QueuedConnection)) {
// 隊列處理} else if (c->connectionType == Qt::BlockingQueuedConnection) {
// 阻塞處理 // 如果同線程,打印潛在死鎖。} else {
//直接調用槽函數或回調函數}

receiverInSameThread表示當前線程id和接收信號的對象的所在線程id是否相等。

如果信號-槽連接方式為QueuedConnection,不論是否在同一個線程,按隊列處理。

如果信號-槽連接方式為Auto,且不在同一個線程,也按隊列處理。

如果信號-槽連接方式為阻塞隊列BlockingQueuedConnection,按阻塞處理。

(注意同一個線程就不要按阻塞隊列調用了。因為同一個線程,同時只能做一件事,本身就是阻塞的,直接調用就好了,

如果走阻塞隊列,則多了加鎖的過程。如果槽中又發了同樣的信號,就會出現死鎖:加鎖之后還未解鎖,又來申請加鎖。)

隊列處理,就是把槽函數的調用,轉化成了QMetaCallEvent事件,通過QCoreApplication::postEvent放進了事件循環。

等到下一次事件分發,相應的線程才會去調用槽函數。

關于事件循環,可以參考之前的文章《Qt實用技能3-理解事件循環》

槽和moc生成

slot函數我們自己實現了,moc不會做額外的處理,所以自動生成的moc_Jerry.cpp文件中,只有Q_OBJECT宏的展開,

和前面的moc_Tom.cpp是一致的,不贅述了。

第三方信號槽實現

信號-槽是非常優秀的通信機制,但Qt的moc實現方式,被一些人詬病,所以他們造了新的輪子,比如:

woboq.com/blog/verdigri

sigslot.sourceforge.net

github.com/NoAvailableA

github.com/pbhogan/Sign

相關鏈接

濤哥的博客:https://jaredtao.github.io

武威濤哥的博客jaredtao.github.io

濤哥的博客-國內鏡像:?https://jaredtao.gitee.io

https://jaredtao.gitee.iojaredtao.gitee.io

高質量交流 QQ群:734623697?

大佬多,不灌水

總結

以上是生活随笔為你收集整理的qt 信号多个链接槽_Qt原理窥探信号槽的实现细节的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。