【Qt信号槽源码分析】

news2024/7/6 18:10:43

Qt信号槽源码分析

  • 一、相关宏介绍
  • 二、示例
    • moc文件源码解析
    • 信号发送接收过程源码解析
      • emit signal
      • connect
  • 三、关键类图:
  • 四、时间&空间问题
  • 五、总结

一、相关宏介绍

*要使用信号-槽功能,先决条件是继承QObject类,并在类声明中增加Q_OBJECT宏*

    在”signals:” 字段之后声明一些函数,这些函数就是信号。而信号只要声明,不需要写实现。这是因为moc会为我们自动生成。另外触发信号时,不写emit关键字,直接调用信号函数,也是没有问题的。这是因为emit是一个空的宏
    在”public slots:” 之后声明的函数,就是槽函数。
signals 关键字:最终被#define 置换为一个访问控制符public,其简化后的语法为#define signals public
slots 关键字:最终被#define 置换为一个空宏,即简化后的语法为:#define slots
emit 关键字:同样被#define 置换为一个空宏,即简化后为:#define emit
在这里插入图片描述
关键的Q_OBJECT宏:
在这里插入图片描述
connect函数连接信号和槽(QObject的静态函数)
disconnect断开槽函数连接

二、示例

这里使用涛哥的Tom-Jerry例子
main.cpp

#include <QCoreApplication>
#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();
}

tom.h

#include <QObject>
#include <QDebug>
class Tom : public QObject
{
    Q_OBJECT
public:
    Tom(QObject *parent = nullptr) : QObject(parent)
    {
    }
    void miaow()
    {
        qDebug() <<  u8"喵!" ;
        emit miao();
    }
signals:
    void miao();
};

jerry.h

#include <QObject>
#include <QDebug>
class Jerry : public QObject
{
    Q_OBJECT
public:
    Jerry(QObject *parent = nullptr) : QObject(parent)
    {
    }
public slots:
    void runAway()
    {
        qDebug() << u8"那只猫又来了,快溜!" ;
    }
};

信号void miao();没有实现,那它是怎么生效的呢?
    这个就要用到Qt的moc机制了,类中使用了Q_OBJECT宏的都会使用moc机制,生成该类对应的静态元对象以及一些元对象的方法。
【注意moc只会读取标记了Q_OBJECT宏的头文件,如果在.cpp中使用了Q_OBJECT,那么就要手动使用moc工具生成对应的头文件"moc_xxx.h"然后在.cpp中include它】

moc文件源码解析

这里以Tom类为例,生成的中间文件(在构建目录的moc文件夹中)如下:

// 直接include我们写的tom.h
#include "/Users/zzgang/master/wps/Coding/shell2/plugins_mac/mytest/src/tom.h"
#include <QtCore/qbytearray.h>
#include <QtCore/qmetatype.h>
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'tom.h' doesn't include <QObject>."
#elif Q_MOC_OUTPUT_REVISION != 67
#error "This file was generated using the moc from 5.12.12. 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);
}

//staticMetaObject是一个结构体,用来存储Tom这个类的信号、槽等元信息,并把
//qt_static_metacall静态函数作为函数指针存储起来。
//因为是静态成员,所以实例化多少个Tom对象,它们的元信息都是一样的。
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 &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 0 信号的实现
void Tom::miao()
{
    QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}
QT_WARNING_POP
QT_END_MOC_NAMESPACE

信号发送接收过程源码解析

emit signal

    信号miao()里面调用了QMetaObject::activate(this, &staticMetaObject, 0, nullptr),这个activate函数中做的主要事情是:
1、先加锁避免连接在其他线程中被disconnnect掉了,

QMutexLocker locker(signalSlotLock(sender))

2、 获取信号发送者的connectionLists,然后遍历该connectionLists中连接的每个receiver,根据连接类型和发送者接受者是否在同一线程来决定使用何种处理方式,这里处理的方式,分为三种:
  • 如果信号-槽连接方式为QueuedConnection,不论是否在同一个线程,按队列处理。
  • 如果信号-槽连接方式为Auto,且不在同一个线程,也按队列处理。
  • 如果信号-槽连接方式为阻塞队列BlockingQueuedConnection,按阻塞处理。
    (注意同一个线程就不要按阻塞队列调用了。因为同一个线程,同时只能做一件事,本身就是阻塞的,直接调用就好了,如果走阻塞队列,则多了加锁的过程。如果槽中又发了同样的信号,就会出现死锁:加锁之后还未解锁,又来申请加锁。)
3、队列处理,就是把槽函数的调用,转化成了QMetaCallEvent事件,通过QCoreApplication::postEvent放进了事件循环。等到下一次事件分发,相应的线程才会去调用槽函数。

if((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
                || (c->connectionType == Qt::QueuedConnection)) {
    // receiverInSameThread表示当前线程id和接收信号的对象的所在线程id是否相等。
    // 队列处理
} else if (c->connectionType == Qt::BlockingQueuedConnection) {
    // 阻塞处理
    // 如果同线程,打印潜在死锁。
} else {
    const int method = c->method_relative + c->method_offset;
    metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);
    // 这个metacall就会调用上面moc文件中的Tom::qt_metacall,最终调到qt_static_metacall
}

    上面的这个connectionLists就是一个成员类型为QMetaObject::Connection的QObjectConnectionListVector,每个发送者都有一个QObjectConnectionListVector对象,发送者的每个信号都有一个接受者的list。
在这里插入图片描述

connect

    在使用QObject::connect函数进行信号槽的连接时,会调用QObjectPrivate::get(s)->addConnection(signal_index, c.data()) 将新的Connection对象添加到list中。当然这个过程也需要加锁:

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

// 通过比较指针大小来保证在多线程获取互斥量的锁时按照相同的顺序获取锁,从而避免死锁的发生。
QOrderedMutexLocker(QMutex *m1, QMutex *m2)
    : mtx1((m1 == m2) ? m1 : (std::less<QMutex *>()(m1, m2) ? m1 : m2)),
      mtx2((m1 == m2) ?  0 : (std::less<QMutex *>()(m1, m2) ? m2 : m1)),
      locked(false){
        if (!locked) {
            if (m1) m1->lock();
asm volatile ("pause" ::: "memory");
            if (m2) m2->lock();
            locked = true;
        }
}
// 更常规的做法其实是在m1->lock()和m2->lock()之间使用
// 内存屏障asm volatile ("pause" ::: "memory");保证操作的顺序一致性

    这就是为什么如果几个槽函数连接到同一个信号上,当信号发出时,这些槽函数将按照它们连接时的顺序依次执行。
    QObject::disconnect的时候会先对sender的整个connectionLists进行标脏,直到全部连接都断开了才会把connectionLists释放掉。

三、关键类图:

在这里插入图片描述
    信号槽机制的好处就在于我们只用关注信号发送接受者的实现,不需要自己去写Connection相关的代码

四、时间&空间问题

时间:(使用gettimeofday函数计时,从信号发出到进入槽函数)
    单个信号槽:微秒级(7us,测试次数比较少,这里是大致的时间~)
在这里插入图片描述
    若工程中多个槽函数连接在同一个信号上,会导致有些槽函数的调用延迟会很高(这里是对实际工程中的某个函数进行测试,接近400ms)
在这里插入图片描述
空间:Connection对象的大小问题:(40字节)
    物理页大小4KB,102个Connection对象就要占1页了,所以用链表比较好

    struct Connection
    {
        QObject *sender;
        QObject *receiver;
        union {
            StaticMetaCallFunction callFunction;
            QtPrivate::QSlotObjectBase *slotObj;
        };
        // The next pointer for the singly-linked ConnectionList
        Connection *nextConnectionList;
        //senders linked list
        Connection *next;
        Connection **prev;
        QAtomicPointer<const int> argumentTypes;
        QAtomicInt ref_; //std::atomic
        ushort method_offset;
        ushort method_relative;
        uint signal_index : 27; // In signal range (see QObjectPrivate::signalIndex())
        ushort connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking
        ushort isSlotObject : 1;
        ushort ownArgumentTypes : 1;
        Connection() : nextConnectionList(nullptr), ref_(2), ownArgumentTypes(true) {
            //ref_ is 2 for the use in the internal lists, and for the use in QMetaObject::Connection
        }
        ~Connection();
        int method() const { Q_ASSERT(!isSlotObject); return method_offset + method_relative; }
        void ref() { ref_.ref(); }
        void deref() {
            if (!ref_.deref()) {
                Q_ASSERT(!receiver);
                delete this;
            }
        }
    };

五、总结

    信号槽的出现,本质上是为了解决对象间的通信问题,它的优点是函数名称、参数类型一目了然,支持多种同/异步连接方式。但是我们也要注意信号槽的滥用问题,就像上述中建立一个槽连接,在时间和空间上都是有损耗的,如果同一个sender上连接的槽函数过多就可能导致性能问题,要是不了解信号槽的本质,可能连性能优化的方向都找不到。
    QT源码里还有许许多多的经典设计值得我们学习,后面还需继续深入探索,加油吧骚年~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1305166.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ue5材质预览界面ue 变黑

发现在5.2和5.1上都有这个bug 原因是开了ray tracing引起的&#xff0c;这个bug真是长时间存在&#xff0c;类似的bug还包括草地上奇怪的影子和地形上的影子等等 解决方法也很简单&#xff0c;就是关闭光追&#xff08;不是…… 就是关闭预览&#xff0c;在材质界面preview sc…

屠宰加工污废水处理工艺设备有哪些

屠宰加工行业对于废水处理的要求日益严格&#xff0c;为了达到环保要求&#xff0c;减少对环境造成的负面影响&#xff0c;屠宰加工污废水处理工艺设备应运而生。以下是常见的几种工艺设备&#xff1a; 1. 沉淀池&#xff1a;沉淀池是屠宰加工废水处理中常用的处理设备之一。废…

【RTOS学习】模拟实现任务切换 | 寄存器和栈的变化

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《RTOS学习》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 目录 &#x1f3c0;认识任务切换&#x1f3d0;切换的实质&#x1f3d0;栈中的内容&#x1f3d0;切…

scala表达式

1.8 表达式&#xff08;重点&#xff09; # 语句(statement)&#xff1a;一段可执行的代码# 表达式(expression)&#xff1a;一段可以被求值的代码&#xff0c;在Scala中一切都是表达式 - 表达式一般是一个语句块&#xff0c;可包含一条或者多条语句&#xff0c;多条语句使用“…

Fiddler如何比较两个接口请求?我来告诉你

进行APP测试时&#xff0c;往往会出现Android和iOS端同一请求&#xff0c;但执行结果不同&#xff0c;这通常是接口请求内容差异所致。 我习惯于用Fiddler抓包&#xff0c;那此时应该如何定位问题呢&#xff1f; 分别把Android和iOS的接口请求另存为TXT文件&#xff0c;然后用…

软件安全设计

目录 一&#xff0c;STRIDE 威胁建模 1&#xff0c;STRIDE 2&#xff0c;总体流程&#xff08;关键步骤&#xff09; 3&#xff0c;数据流图的4类元素 二&#xff0c;安全设计原则 三&#xff0c;安全属性 一&#xff0c;STRIDE 威胁建模 1&#xff0c;STRIDE STRIDE 是…

区块链实验室(32) - 下载arm64的Prysm

Prysm是Ethereum的共识层。 1. 下载prysm.sh curl https://raw.githubusercontent.com/prysmaticlabs/prysm/master/prysm.sh --output prysm.sh && chmod x prysm.sh2. 下载x86版prysm共识客户端 ./prysm.sh beacon-chain --download-only3.下载arm64版prysm共识客…

论文解读:Medical Transformer论文创新点解读

这篇文章其实就是基于Axial-DeepLab: Stand-Alone Axial-Attention forPanoptic Segmentation论文上进行的一些小创新 Stand-Alone Axial-Attention forPanoptic Segmentation论文解读&#xff1a; 论文解读&#xff1a;Axial-DeepLab: Stand-Alone Axial-Attention forPanop…

K8s可视化kuboard 部署

创建资产 [rootkube-master ~]# kubectl apply -f https://addons.kuboard.cn/kuboard/kuboard-v3.yaml 查看对应资源 [rootkube-master ~]# kubectl get pod -n kuboard NAME READY STATUS RESTARTS AGE kuboard-agent-2-5c4f886…

InnoDB在SQL查询中的关键功能和优化策略

文章目录 前言存储引擎介绍存储引擎是干嘛的InnoDB的体系结构 InnoDB的查询操作InnoDB的查询原理引入 Buffer Pool引入数据页Buffer Pool 的结构数据页的加载Buffer Pool 的管理Buffer Pool 的优化 总结 前言 通过上篇文章《MySQL的体系结构与SQL的执行流程》了解了SQL语句的执…

初入职场的你,为何会频繁跳槽?

大数据统计&#xff0c;初入职场的人跳槽频率相当高&#xff0c;而对于工作了2~3年的来说&#xff0c;跳槽频率也就没有那么频繁了&#xff0c;是什么原因导致了频繁跳槽&#xff1f;如何避免频繁跳槽呢&#xff1f; 是什么原因导致了跳槽&#xff1f; 不适应 从学校毕业&am…

创建第一个SpringBoot项目

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; 循序渐进学SpringBoot ✨特色专栏&…

IO第二天作业

1.用read write函数实现文件拷贝 程序 #include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h> #include <stdlib.h> #include <string.h>int main(int argc, const char *argv[]){…

练练手之“四环”“磁铁”(svg)

文本是闲暇之余练习svg的运用的产物&#xff0c;记录以备有需。 <svg xmlns"http://www.w3.org/2000/svg" viewBox"0 0 500 500" width"500px" height"500px"><path d"M150,100 A50,50 0 1,1 150,99.999" stroke&q…

1836_emacs显示空白字符

Grey 全部学习汇总&#xff1a; GitHub - GreyZhang/editors_skills: Summary for some common editor skills I used. 全部学习内容汇总&#xff1a; 1836_emacs显示空白字符 show-trailing-whitespace是emacs中内置的一个变量&#xff0c;这个变量的值如果设置为nil那么不…

国内几款常用热门音频信号处理电路芯片--低噪声,高增益

随着智能手机、汽车音频、AI智能音箱&#xff0c;智能家居、家庭影院、平板电脑、笔记本电脑等智能设备的普及&#xff1b;数字音频功放芯片的应用也越来越广泛&#xff1b;同时对音频信号处理的芯片的性能要求越来越高&#xff1b;以下几款就是常用热门音频信号处理电路芯片分…

Leetcode—2963.统计好分割方案的数目【困难】

2023每日刷题&#xff08;五十七&#xff09; Leetcode—2963.统计好分割方案的数目 算法思想 参考灵神思路 实现代码 class Solution { public:long long mod 1e97;long long pow(long long x, int cnt) {if(cnt 0) {return 1;}if(cnt 1) {return x % mod;}long long …

知网查重重复率多少标红 神码ai

大家好&#xff0c;今天来聊聊知网查重重复率多少标红&#xff0c;希望能给大家提供一点参考。 以下是针对论文重复率高的情况&#xff0c;提供一些修改建议和技巧&#xff1a; 知网查重重复率多少标红 在论文撰写过程中&#xff0c;我们常常需要使用各种查重工具来检测论文的…

电脑技巧:Windows右键菜单增强工具FileMenu Tools介绍

目录 一、为Win10系统添加FileMenu Tools右键菜单组 二、自定义添加FileMenu Tools右键菜单项 五、“发送到…”右键菜单&#xff1a; 六、管理第三方程序添加的右键菜单项 七、获取某个FileMenu Tools菜单项的使用命令 八、软件下载 FileMenu Tools 是一款强大的 Window…

基于OpenCV+CNN+IOT+微信小程序智能果实采摘指导系统——深度学习算法应用(含python、JS工程源码)+数据集+模型(二)

目录 前言总体设计系统整体结构图系统流程图 运行环境Python环境TensorFlow 环境Jupyter Notebook环境Pycharm 环境微信开发者工具OneNET云平台 相关其它博客工程源代码下载其它资料下载 前言 本项目基于Keras框架&#xff0c;引入CNN进行模型训练&#xff0c;采用Dropout梯度…