Qt 信号和槽原理分析

简 述: Qt 的信号和槽原理分析:手写一个 moc 预编译器模拟生成 mo_xxx.cpp 过程


本文初发于 “偕臧的小站” ifmet.cn,同步转载于此。


背景

最近手工模拟了 Qt 的信号和槽实现原理,用纯 C++ 实现来实现一个 connet 函数。我的 💻 环境为: uos20 amd64 📎 Qt 5.11.3 📎 gcc/g++ 9.0 📎 gdb8.0

Object::db_connet(obj1, "sig1()", obj2, "slot1()");

原理

Qt 的信号和槽解决了 GUI 开发过程中 “对象间通信和共享数据” 问题。属于 Qt 的一个独创解决思路。关于为啥不用 C++ 的标准智能指针或者其它库呢??那时候 Boost 和 stl 都没诞生呢。所以代价就是通过 moc 预编译器来扩展 C++ 语法。

​ 模拟 object.h 通过 moc 生成 db_object.cpp 的过程中,要想实现如下的一个 connet 函数,很重要就是解决 A 对象的 n 信号,映射关联到 B 对象的 m 槽函数

static void db_connet(Object *sender, const char *sig, Object *receiver, const char *slot);

object.h

下图 的object.h 模拟 QObject 类;

  • 自定义 C++ 之外的关键字 db_signalsdb_slotsdb_emit;编译器不报错的?添加为宏预定义处理即可。
  • MetaObject 对象模拟是元对象,用来在 object 中用来存储 db_signalsdb_slots 标记下面的函数名;对于信号,只需要声明,则不需要定义(通过 moc 自动在db_object.cpp 中生成 ),但是槽函数则需要自己在其它函数中声明和定义
  • Connection 是做作为 ConnectionMap 的 value 使用的。保存本对象所链接的对象和对应的槽函数
  • ConnectionMap “对象 + 信号 --------映射------> 对象 + 槽” 在代码中存储表现形式。
  • ConnectionMapIt 是 ConnectionMap 遍历的游标。
  • static MetaObject meta 具体的元对象。
  • metacall 通过槽的索引 ==> 槽函数,然后调用
  • static void db_connet() 静态函数设计,可在任何地方都可以使用,连接信号和槽函数
  • void testSignal() 测试信号函数,看槽函数是否会成功被调用,即 emit signal( )
#ifndef OBJECT_H
#define OBJECT_H

#include <iostream>
#include <map>
using namespace std;

#define db_signals protected
#define db_slots
#define db_emit

class Object;
struct MetaObject              // 元对象
{
    const char *sig_names;
    const char *slot_names;

    static void active(Object *sender, int idx);
};

struct Connection
{
    Object *recviver;
    int method;
};

typedef multimap<int, Connection> ConnectionMap;
typedef multimap<int, Connection>::iterator ConnectionMapIt;

class Object
{
    static MetaObject meta;
    void metacall(int idx);

public:
    static void db_connet(Object *sender, const char *sig, Object *receiver, const char *slot);
    void testSignal();

public:
    Object();
    virtual ~Object();

db_signals:
    void sig1();
//    void sig2();

public db_slots:
    void slot1();
//    void slot2();

friend class MetaObject;
private:
    ConnectionMap connectionsMap;
};

#endif // OBJECT_H

object.cpp

object 类的实现细节

#include "object.h"
#include <cstring>

static int findSignalIndex(const char *str, const char *subStr)
{
    if (!str || !subStr || strlen(str) < strlen(subStr))
        return -1;

    int ret = strcmp(str, subStr);
    if (ret == 0)
        return ret;
    else
        return -1;
}

void Object::db_connet(Object *sender, const char *sig, Object *receiver, const char *slot)
{
    int sig_idx = findSignalIndex(sender->meta.sig_names, sig);
    int slot_idx = findSignalIndex(receiver->meta.slot_names, slot);

    if (sig_idx == -1 || slot_idx == -1) {
        cout<<"signal or slot not found!";
        return;
    } else {
        Connection c = {receiver, slot_idx};
        sender->connectionsMap.insert(pair<int, Connection>(sig_idx, c));  // connectionsMap 私有成员
    }
}

void Object::testSignal()
{
    db_emit sig1();
}

Object::Object()
{
}

Object::~Object()
{
}

// 通过 sender 的信号 idx ==> 槽函数
void MetaObject::active(Object *sender, int idx)
{
    pair<ConnectionMapIt, ConnectionMapIt> ret;
    ret = sender->connectionsMap.equal_range(idx);  // 寻找[idx,  )
    for (ConnectionMapIt it = ret.first; it != ret.second; ++it) {
        Connection c = (*it).second;
        c.recviver->metacall(idx);
    }
}

db_object.cpp

object 类通过 moc 生成 db_object.cpp,一些 moc 所执行的代码扩展在 db_object.cpp 里面。

#include "object.h"

//db_object: 是由 moc 编译器 将 object.cpp 展开的内容(此处手写表示)

const char sig_names[] = "sig1()";
const char slot_names[] = "slot1()";
MetaObject Object::meta = {sig_names, slot_names};

void Object::sig1()
{
    MetaObject::active(this, 0);
}

void Object::slot1()
{
    cout << "-----------> this is slot1()";
}

// 槽的索引==> 槽函数
void Object::metacall(int idx)
{
    switch (idx) {
    case 0:
        slot1();
        break;
    default:
        break;
    }
}

main.cpp

#include <iostream>
#include "object.h"
using namespace std;

// 目的:自行构造 moc 编译器,手动将 object.h --> db_bject.cpp (宏 和 moc 编译器处理的部分)
// 时间:2021-03-26
// 下载:https://github.com/xmuli/QtExamples

int main(int argc, char *argv[])
{
    Object *obj1 = new Object();
    Object *obj2 = new Object();

    Object::db_connet(obj1, "sig1()", obj2, "slot1()");
    obj1->testSignal();
    return 0;
}

// 终端输出打印:
----------> this is slot1()

总结

本文参考 文一文二 ,但实际发现其源码之间有几处错误,实际运行失败,参考思路重写,方便后来者学习,且本文还有改进之处,日后有空改进,修改点如下:

  • findSignalIndex 函数重写,使得能够识别信号和槽函数的重载函
  • db_connet 在重构,支持宏方式(Qt4)和 函数指针(Qt5)方式;(当前为使用字符串作为参数)
  • 构建 xxx_p.h,将成员变量都放在此文件中,加快项目的编译速度。

系列地址

QtExamples 【DbSigSlot】

欢迎 starfork 这个系列的 QT / DTK 学习,附学习由浅入深的目录。

偕臧x CSDN认证博客专家 架构 Qt/C++ Linux
看待世界始终保持着好奇;期待与各位的邂逅,比较喜欢Linux、C++、Qt和与技术无关的生活相关,不时折腾一下新技术,欢迎来此处https://ifmet.cn 找我玩
相关推荐
<p> <span style="font-size:14px;color:#E53333;">限时福利1:</span><span style="font-size:14px;">购课进答疑群专享柳峰(刘运强)老师答疑服务</span> </p> <p> <br /> </p> <p> <br /> </p> <p> <span style="font-size:14px;"></span> </p> <p> <span style="font-size:14px;color:#337FE5;"><strong>为什么需要掌握高性能的MySQL实战?</strong></span> </p> <p> <span><span style="font-size:14px;"><br /> </span></span> <span style="font-size:14px;">由于互联网产品用户量大、高并发请求场景多,因此对MySQL的性能、可用性、扩展性都提出了很高的要求。使用MySQL解决大量数据以及高并发请求已经是程序员的必备技能,也是衡量一个程序员能力薪资的标准之一。</span> </p> <p> <br /> </p> <p> <span style="font-size:14px;">为了让大家快速系统了解高性能MySQL核心知识全貌,我为你总结了</span><span style="font-size:14px;">「高性能 MySQL 知识框架图」</span><span style="font-size:14px;">,帮你梳理学习重点,建议收藏!</span> </p> <p> <br /> </p> <p> <img alt="" src="https://img-bss.csdnimg.cn/202006031401338860.png" /> </p> <p> <br /> </p> <p> <span style="font-size:14px;color:#337FE5;"><strong>【课程设计】</strong></span> </p> <p> <span style="font-size:14px;"><br /> </span> </p> <p> <span style="font-size:14px;">课程分为四大篇章,将为你建立完整的 MySQL 知识体系,同时将重点讲解 MySQL 底层运行原理、数据库的性能调优、高并发、海量业务处理、面试解析等。</span> </p> <p> <span style="font-size:14px;"><br /> </span> </p> <p> <span style="font-size:14px;"></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;"><strong>一、性能优化篇:</strong></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;">主要包括经典 MySQL 问题剖析、索引底层原理事务与锁机制。通过深入理解 MySQL 的索引结构 B+Tree ,学员能够从根本上弄懂为什么有些 SQL 走索引、有些不走索引,从而彻底掌握索引的使用优化技巧,能够避开很多实战中遇到的“坑”。</span> </p> <p style="text-align:justify;"> <br /> </p> <p style="text-align:justify;"> <span style="font-size:14px;"><strong>二、MySQL 8.0新特性篇:</strong></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;">主要包括窗口函数通用表表达式。企业中的许多报表统计需求,如果不采用窗口函数,用普通的 SQL 语句是很难实现的。</span> </p> <p style="text-align:justify;"> <br /> </p> <p style="text-align:justify;"> <span style="font-size:14px;"><strong>三、高性能架构篇:</strong></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;">主要包括主从复制读写分离。在企业的生产环境中,很少采用单台MySQL节点的情况,因为一旦单个节点发生故障,整个系统都不可用,后果往往不堪设想,因此掌握高可用架构的实现是非常有必要的。</span> </p> <p style="text-align:justify;"> <br /> </p> <p style="text-align:justify;"> <span style="font-size:14px;"><strong>四、面试篇:</strong></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;">程序员获得工作的第一步,就是高效的准备面试,面试篇主要从知识点回顾总结的角度出发,结合程序员面试高频MySQL问题精讲精练,帮助程序员吊打面试官,获得心仪的工作机会。</span> </p>
©️2020 CSDN 皮肤主题: 猿与汪的秘密 设计师:白松林 返回首页