QT信号槽原理(一)connect函数

2023-10-31

2个类型和1个签名

Qt::ConnectionType

信号的连接类型

  • Qt::DirectConnection
    直接连接,相当于在emit信号的地方直接调用接收者的槽函数,多见于在同一线程中去连接信号和槽,是一个同步的调用过程。
  • Qt::QueuedConnection
    队列连接,在emit信号时,会往接收者所在线程的消息队列post一个QMetaCallEvent,接收者线程在未来某个时刻处理消息队列中的该事件时就会调用接收者的槽函数,是一个异步的调用过程。这种方式下,如果信号的参数是个原始指针,那需要确保原始指针的生命周期直到事件被处理时仍然有效;其他类型(包括引用)的参数,qt会自动去克隆。还要注意的是,该类型下,信号的参数会放到event中,所以参数类型比如是QMetatype,如果不是,需要使用qRegisterMetaType()去注册。
  • Qt::BlockingQueuedConnection
    也是队列连接,不同于QueuedConnection在post事件之后立马返回,BlockingQueuedConnection在post事件之后,会一直阻塞直到该事件被处理。所以要注意,当发送者和接收者在同一线程时,千万布套使用这种方式,不然就会导致线程死锁。另外,虽然这是队列连接,但是由于是阻塞的,所以可以看成是同步的,也就不存在参数的生命周期问题。
  • Qt::AutoConnection
    自动连接,如果发送者和接收者在同一线程,那么就是DirectConnection,否则就使用QueuedConnection。
  • Qt::UniqueConnection
  • 严格来说,这并不是一种连接方式,而仅仅是一个掩码,表示唯一连接,它可以与上面四种连接方式以 | 的方式组合,比如 Qt::DirectConnection|Qt::UniqueConnection。如果发送者的信号已于接收者的槽连接过,再进行连接时就会失败。

QMetaMethod::MethodType

类中方法的类型

  • QMetaMethod::Method
    是用invoke修饰的成员函数
  • QMetaMethod::Signal
    信号
  • QMetaMethod::Slot
    槽函数
  • QMetaMethod::Constructor
    构造函数

方法签名

QMetaMethod的methodSignature()返回方法的签名,签名是个字符串。
下面是QMetaMethod获取方法签名的源码:

QByteArray QMetaMethodPrivate::signature() const
{
    QByteArray result;
    result.reserve(256);
    result += name();
    result += '(';
    QList<QByteArray> argTypes = parameterTypes();
    for (int i = 0; i < argTypes.size(); ++i) {
        if (i)
            result += ',';
        result += argTypes.at(i);
    }
    result += ')';
    return result;
}

可以看出签名实际是方法声明去掉返回值和参数名称后的字符串。
例如:
信号声明:
void clicked(bool state)
那么信号的签名就是clicked(bool)

connect函数源码解析

函数原型:

QMetaObject::Connection QObject::connect(const QObject *sender, const QMetaMethod &signal, const QObject *receiver, const QMetaMethod &method, Qt::ConnectionType type)
五个参数:发送者,信号,接收者,槽函数或信号,连接类型

第一步:参数检查

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

发送者和接收者不能为空这是基本条件,另外,第二个参数必须是信号,第四个参数不能是构造函数(从第一节可以看出,除了构造函数外,还有信号、槽函数、普通成员函数三种类型)。

第二步:获取信号和槽的index

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

通过QMetaObjectPrivate::memberIndexes获取信号或槽的index,然后再去检查index。
从moc后的代码来看,信号和槽的index,从信号开始,按声明顺序从0开始依次递增的排列,然后接着是槽声明的顺序,接着信号最后一个序号排列。
例如下面的类声明:

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

signals:
    void testSignal(const QString& name, int age);

public slots:
    void testSlot(const QString& name, int age);
};

信号testSignal的index为0, 槽函数testSlot的index为1。

第三步:检查信号和槽的参数是否匹配

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

这里的比较基于这样一个要求:槽函数参数个数≤信号参数个数,且参数类型和顺序必须一致。想想也很容易明白,槽函数参数少,那信号多出来的参数可以不传下去,但是如果槽函数参数多的话,那多出来的参数应该传什么值呢。
在第一节我们说过方法签名的组成:方法名称(参数类型1,参数类型2,…),下面看checkConnectArgs源码:

bool QMetaObject::checkConnectArgs(const char *signal, const char *method)
{
    const char *s1 = signal;
    const char *s2 = method;
    while (*s1++ != '(') { }                        // scan to first '('
    while (*s2++ != '(') { }
    if (*s2 == ')' || qstrcmp(s1,s2) == 0)        // method has no args or
        return true;                                //   exact match
    int s1len = qstrlen(s1);
    int s2len = qstrlen(s2);
    if (s2len < s1len && strncmp(s1,s2,s2len-1)==0 && s1[s2len-1]==',')
        return true;                                // method has less args
    return false;
}

按上面提到的比较要求,比较参数列表的话,就是看括号里的参数列表字符串,如果槽函数参数列表字符串是信号参数列表字符串的子串或者相同,那ok,否则fail。

第四步:检查信号参数类型

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

在前面讲连接类型我们提到如果是队列连接,参数类型比如是QMetaType或者是注册到QMetaType。这一步就是检查参数类型是否是QMetaType。
那BlockingQueuedConnection也是队列连接,为啥不需要检查参数类型呢?
那是因为虽然BlockingQueuedConnection是队列连接,但是通过阻塞变成了同步调用,所以不存在参数生命周期问题,所以不会去克隆信号参数,所以也就不需要是QMetaType。

第五步:创建连接对象

QMetaObject::Connection handle = QMetaObject::Connection(QMetaObjectPrivate::connect(
    sender, signal_index, signal.enclosingMetaObject(), receiver, method_index, 0, type, types));
return handle;

所有的检查都通过后,就通过QMetaObjectPrivate::connect来创建连接对象。
这个函数主要分3小步:

检查Qt::UniqueConnection

    QObjectPrivate::ConnectionData *scd  = QObjectPrivate::get(s)->connections.loadRelaxed();
    if (type & Qt::UniqueConnection && scd) {
        if (scd->signalVectorCount() > signal_index) {
            const QObjectPrivate::Connection *c2 = scd->signalVector.loadRelaxed()->at(signal_index).first.loadRelaxed();

            int method_index_absolute = method_index + method_offset;

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

如果连接类型带了UniqueConnection掩码,则检查当前是否符合UniqueConnection。

创建connection对象,并填充各个数据

    std::unique_ptr<QObjectPrivate::Connection> c{new QObjectPrivate::Connection};
    c->sender = s;
    c->signal_index = signal_index;
    c->receiver.storeRelaxed(r);
    QThreadData *td = r->d_func()->threadData;
    td->ref();
    c->receiverThreadData.storeRelaxed(td);
    c->method_relative = method_index;
    c->method_offset = method_offset;
    c->connectionType = type;
    c->isSlotObject = false;
    c->argumentTypes.storeRelaxed(types);
    c->callFunction = callFunction;

创建Connection对象,填充各个数据成员。

将connection对象添加到sender中

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

将上一步创建的connection对象添加到sender的连接列表中。
到此,一个完整的connect执行流程就分析完成了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

QT信号槽原理(一)connect函数 的相关文章

随机推荐

  • spdlog用法

    转自 spdlog学习笔记 说明 所有内容翻译自spdlog的wiki 受英语水平所限 有所错误或失真在所难免 如果您有更好的建议 请在博文下留言 线程安全 spdlog 命名空间下的是线程安全的 当loggers在不同的线程同时执行时 下
  • Django框架之DRF序列化与反序列化

    概念 序列化 将Python对象转换为json字符串 反之json字符串反序列化为对象 使用drf序列化组件的步骤如下 1 在你的app中新建一个py文件 新建一个序列化的类 2 在类中写要序列化的字段 在视图中使用序列化的类 1 实例化出
  • 从控制台输入基本工资,并计算输出实领工资

    题目 编写程序 public class Test01 这是一个main方法 程序的入口 public static void main String args 键盘输入 Scanner input new Scanner System i
  • 苹果电脑适合python吗_m1的MacBook pro适合python开发吗?

    我的MacBook Air M1到手了 我自己常用的python包都装了 都是arm的版本 不是用Rosseta跑的 请参考 短暂使用还没有发现什么BUG 只不过numpy和tf相关的包是Apple提供的 opencv也不能直接pip或者c
  • svm实现二分类_支持向量机原理详解(三): 核函数与非线性SVM

    前文推导了 线性 SVM的对偶问题 见支持向量机原理详解 二 拉格朗日对偶函数 SVM的对偶问题 不过 线性SVM以超平面来划分两类数据 如果数据本身是非线性的 那么以超平面作为决策边界就显得不太适用了 通过引入核函数 可以使SVM适用于非
  • 防雷工程专业术语及雷电浪涌保护器名词解释

    1 雷电次数 当雷暴进行时 隆隆的雷声持续不断 若其间雷声的时间间隔小于15分钟时 不论雷声断续传播的时间有多长 均算作是一次雷暴 若其间雷声的停息时间在15分钟以上时 就把前后分作是两次雷暴 2 雷电小时 就是说在该天文小时内发生过雷暴
  • springboot+mybatis+druid+postgreSql案例

    springboot mybatis druid postgreSql mysql 示例 springboot mybatis druid postgreSql mysql 示例 简介 结构解析 代码剖析 entity层 Dao层 serv
  • Java-System、Math、BigInteger、BigDecimal常用类的使用

    Java System Math BigInteger BigDecimal常用类的使用 1 System类 System类代表系统 系统级的很多属性和控制方法都放置在该类的内部 该类位于java lang包 由于该类的构造器是privat
  • 解决Linux7 ping出现 未知的名称或服务 错误

    问题描述 Linux7在利用命令nmtui进入图形化界面配置Linux的网络后为测试是否成功联网 ping配置的IP成功但ping如www baidu com常见网站时出现 gt ping www baidu com 未知的名称或服务 解决
  • 网络编程知识预备(5) ——libcurl库安装及其编程访问百度首页(断点续传)

    本文为学习笔记 整合课程内容以及下列文章 其中 libcurl函数库常用字段解读部分 参考博文 原文地址 作者 冬冬他哥哥 目录 Libcurl库简介 Libcurl等三方库的通用编译方法 三方库使用前通读方法 库的配置 编译 安装 调用l
  • protobuf生成prototxt文件

    使用protobuf可以分为以下几步 1 proto文件的定义 在这个文件中定义了最终生成的prototxt格式 举个例子如下所示 syntax proto2 package label proto message DetectLabel
  • scrapy缺点

    Scrapy 是一个功能强大的网络爬虫框架 但也有一些缺点 Scrapy 只能爬取静态网页 无法爬取动态网页 Scrapy 只能爬取一个网站的数据 无法爬取多个网站的数据 Scrapy 需要编写代码 对于那些不会编写代码的人来说可能会有些困
  • LiteOS内存管理

    1 内存管理简介 内存管理模块管理系统的内存资源 它是操作系统的核心模块之一 主要包括内存的初始化 分配以及释放 在系统运行过程中 内存管理模块通过对内存的申请 释放操作 来管理用户和OS对内存的使用 使内存的利用率和使用效率达到最优 同时
  • obs无法连接服务器?PotPlayer获取不到视频

    搭建基于nginx的rtmp直播服务器 https hywlovexyc info blog archives 572 rtmp server listen 1935 监听的端口 chunk size 4000 application yu
  • 删除中间节点

    题目 若链表中的某个节点 既不是链表头节点 也不是链表尾节点 则称其为该链表的 中间节点 假定已知链表的某一个中间节点 请实现一种算法 将该节点从链表中删除 例如 传入节点 c 位于单向链表 a gt b gt c gt d gt e gt
  • sqlserver查看数据表结构

    可以通过两种方式 1 sp help 表名 2 sp columns 表名
  • 我的世界服务器config文档,Essentials/配置文件/config.yml

    ops name color none nickname prefix max nick length 15 change displayname true change playerlist true add prefix suffix
  • Unity--虚拟轴

    1 查看虚拟轴 Edit gt project settings gt Input 2 获取虚拟轴 input GetAxis 用法 GetAxis Mouse X GetAxis Mouse Y GetAxis Mouse ScrollW
  • centos7Linux中的lvm、pv、vg、lv,以及Linux扩容,创建新分区

    概念 本着言简意赅的目的 以下概念会在扩容时用到 需熟知 不然会不理解具体操作在干什么 lvm 逻辑卷管理器 Logical Volume Manager 它使系统管理员可以更方便的为应用与用户分配存储空间 在LVM管理下的存储卷可以按需要
  • QT信号槽原理(一)connect函数

    目录 2个类型和1个签名 Qt ConnectionType QMetaMethod MethodType 方法签名 connect函数源码解析 函数原型 第一步 参数检查 第二步 获取信号和槽的index 第三步 检查信号和槽的参数是否匹