Qt信号槽的回调机制

问:Qt强大的地方在哪里?

答:跨平台、信号槽。。。

问:信号槽是什么?

答:回调函数

问:怎么个回调法子

答:。。。

成果

        信号槽本身实现过程是有些复杂的,所以本人参考了很早很早很早版本的Qt 1.41。目的很简单,就是想看看信号槽究竟是怎么回调的。先看看咱的仿信号槽成果:

两个测试类,一个用来发信号,一个用来响应信号

//TestSignal.h
#pragma once
#include "Mobject.h"
class TestSignal : public Mobject
{M_OBJECT
public:TestSignal(){}MySignals:void signalEvent();
};//TestSignal.cpp
#include "TestSignal.h"//你没看错, TestSignal.cpp就是啥都没有
//TestSlot.h
#pragma once
#include "Mobject.h"
class TestSlot : public Mobject
{M_OBJECT
public:TestSlot() {}public MySlots :void slotEvent();
};//TestSlot.cpp
#include "TestSlot.h"
#include <stdio.h>void TestSlot::slotEvent()
{printf("slotEvent invoked\n");
}

测试信号槽关联

#include <iostream>
#include <thread>
#include <Windows.h>
#include "TestSignal.h"
#include "TestSlot.h"int main()
{//用来触发信号TestSignal* sig = new TestSignal;//用来响应信号的槽TestSlot* slot = new TestSlot;//信号和槽建立关联Mobject::connect(sig, SIGNAL(signalEvent()), slot, SLOT(slotEvent()));//测试开始std::thread t1([&sig]() {while (true) {Sleep(1000);MyEmit sig->signalEvent();}});t1.join();
}

结果:

槽被成功触发了,完结撒花~~ (才怪)

正文

实现一个我们自己的QObject, 就叫Mobject吧,只写一些信号槽机制相关的宏和成员,其他没啥关系的成员我们就不要了。 

#pragma once
#include <map>#define SIGNAL(a) "2"#a
#define SLOT(a) "1"#a#define MySignals public
#define MySlots
#define MyEmitclass MetaObject;
class Connection;
#define M_OBJECT \
public:	\MetaObject *metaObject() const { return metaObj; }	\
protected: \void	 initMetaObject();	\
private: \static MetaObject *metaObj;//基类  仿QObject
class Mobject
{
public:Mobject(){}~Mobject(){}virtual MetaObject* metaObject() const { return metaObj; }virtual void initMetaObject();virtual MetaObject* queryMetaObject() const;void active_signals(const char* signal);static bool connect(const Mobject *sender, const char *signal,const Mobject *receiver, const char *member);static MetaObject* metaObj;std::map<std::string, Connection*> conns_;
};
#include "Mobject.h"
#include "MetaObject.h"MetaObject* Mobject::metaObj = nullptr;void Mobject::initMetaObject()
{metaObj = new MetaObject(nullptr, nullptr);
}MetaObject * Mobject::queryMetaObject() const 
{Mobject *x = (Mobject*)this;MetaObject* m = x->metaObject();if (m == nullptr)x->initMetaObject();m = x->metaObject();if (m)return m;elsereturn nullptr;
}void Mobject::active_signals(const char * signal)
{auto it = conns_.find(signal);if (it == conns_.end())return;typedef void (Mobject::*RT)();Connection* c = it->second;Mobject* obj = c->obj_;RT r = *((RT*)(&c->mbr_));(obj->*r)();
}bool Mobject::connect(const Mobject *sender, const char *signal,const Mobject *receiver, const char *member)
{/*跳过检查数据的正确性*///MetaObject* sMeta = sender->queryMetaObject();MetaObject* rMeta = receiver->queryMetaObject();signal++; //去掉前面的 2member++; //去掉前面的 1MetaData* rm = rMeta->slot(member);Connection* c = new Connection(receiver, rm->ptr, rm->name);((Mobject*)sender)->conns_.insert({std::string(signal), c });return true;
}

SIGNAL 和 SLOT 完全照抄,就是在信号函数前面加上一个“2”,槽函数前面加上一个“1”,这两个值就是为了标记区分信号和槽的。

MySignals 用来定义一个信号

MySlots 用来定义一个槽函数

MyEmit 用来定义发射信号

M_OBJECT 就是缩减版的Q_OBJECT 宏

成员函数:

active_signals  发射信号其实就是调用的这个函数,它内部会找到关联的槽函数,并调用槽函数,当然我们这里只是为了了解过程,所以仅仅只调用了一个槽函数。

connect 函数是建立信号和槽的主要实现。

再来看下MetaObject类

#pragma once
#include "Connection.h"
#include <map>struct MetaData {char* name;MemberPtr ptr;
};class MetaObject
{
public:MetaObject(MetaData *slots, MetaData *signals);MetaData* slot(const char*);MetaData* signal(const char*);std::map<std::string, MetaData*> slotds_;std::map<std::string, MetaData*> signalds_;
};
#include "MetaObject.h"MetaObject::MetaObject(MetaData *slots, MetaData *signals)
{if (signals)signalds_.insert({ std::string(signals->name), signals });if(slots)slotds_.insert({ std::string(slots->name), slots });
}MetaData * MetaObject::slot(const char * name)
{auto it = slotds_.find(name);if (it == slotds_.end())return nullptr;return it->second;
}MetaData * MetaObject::signal(const char * name)
{auto it = signalds_.find(name);if (it == signalds_.end())return nullptr;return it->second;
}

可以看到,它扮演了Object的助手职责,后续会通过moc_xxx.cpp来实现记录类中定义的信号和槽。

Connection辅助类

#pragma once
#include "Mobject.h"
typedef void (Mobject::*MemberPtr)();class Connection
{
public:Connection(const Mobject*, MemberPtr, const char* memberName);~Connection(){}Mobject *obj_;MemberPtr mbr_;const char* mbrName_;
};
#include "Connection.h"Connection::Connection(const Mobject *obj, MemberPtr mbr, const char * memberName)
{obj_ = (Mobject*)obj;mbr_ = mbr;mbrName_ = memberName;
}

obj_ 对象指针

mbr_ 成员函数

mbrName_ 成员函数标识,一般就是对应着 SIGNAL(xxx) 和 SLOT(xxx)。

有了这些基础设施。再来手动实现moc.exe的功能,手动生成TestSignal.h对应的moc_TestSignal.cpp 和 TestSlot.h对应的moc_TestSlot.cpp

#include "TestSignal.h"
#include "MetaObject.h"MetaObject* TestSignal::metaObj = nullptr;
void TestSignal::initMetaObject()
{if (metaObj)return;typedef void(TestSignal::*m2_t0)();m2_t0 s0 = &TestSignal::signalEvent;MetaData *signal_tbl = new MetaData();signal_tbl->name = _strdup("signalEvent()");signal_tbl->ptr = *(MemberPtr*)&s0;metaObj = new MetaObject(nullptr, signal_tbl);
}void TestSignal::signalEvent()
{this->active_signals("signalEvent()");
}
#include "TestSlot.h"
#include "MetaObject.h"MetaObject* TestSlot::metaObj = nullptr;
void TestSlot::initMetaObject()
{if (metaObj)return;typedef void(TestSlot::*m2_t0)();m2_t0 s0 = &TestSlot::slotEvent;MetaData *slot_tbl = new MetaData();slot_tbl->name = _strdup("slotEvent()");slot_tbl->ptr = *(MemberPtr*)&s0;metaObj = new MetaObject(slot_tbl, nullptr);
}

就是把 M_OBJECT宏里面的 initMetaObject给实现出来,把定义的信号函数自动实现下,信号和槽通过initMetaObject函数都记录到metaObj中。

以上就是精简过很多以后的仿Qt信号槽实现的全过程了。能跟着调试器一步步看看运行过程更能很好的理解。源码中有很多检查校验函数,善后释放都没有去实现,毕竟我们的目标是理解信号槽的机制。

完整工程示例

https://download.csdn.net/download/hanzhaoqiao1436/89431950

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

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

相关文章

50【Aseprite 作图】模糊工具 笔刷

1 模糊工具 2 笔刷 然后 选中 后 Ctrl B&#xff0c;就变成笔刷了 可以按住shift &#xff0c;像画一条线一样 或者用矩形、圆形工具、油漆桶工具 在上方可以选择笔刷的不同形式&#xff0c;如果是“图案与来源对齐”&#xff0c;就是来源不变&#xff0c;笔刷不会覆盖之前…

每日5题Day24 - LeetCode 116 - 120

每一步向前都是向自己的梦想更近一步&#xff0c;坚持不懈&#xff0c;勇往直前&#xff01; 第一题&#xff1a;116. 填充每个节点的下一个右侧节点指针 - 力扣&#xff08;LeetCode&#xff09; /* // Definition for a Node. class Node {public int val;public Node left;…

LeetCode | 171.Excel表列序号

这道题涉及到字符串和进制转换&#xff0c;首先我们先创建一个A-Z到1-26的map映射&#xff0c;方便我们后续遍历字符串转换&#xff0c;然后对字符串从后往前遍历&#xff0c;依次加上对应权重&#xff0c;注意越往前的权重越大&#xff0c;要记得对应乘上26的对应方数 class …

Nginx 搭建 lnmp

一.编译安装Nginx 1.新建用户前期准备 官网下载nginx安装包 https://nginx.org/en/download.html yum -y install gcc pcre-devel openssl-devel zlib-devel openssl openssl-devel #安装依赖包 useradd -M -s /sbin/nologin nginx #新建nginx用户便于管理 2.切换到/opt…

自动控制理论---线性时不变系统的单位脉冲响应

1、实验设备 PC计算机1台&#xff0c;MATLAB软件1套。 2.实验目的&#xff1a; 学习并理解线性时不变系统的单位脉冲响应的计算方法。掌握MATLAB编程&#xff0c;计算整个系统的单位脉冲响应。 3.实验原理说明&#xff1a; 单位脉冲响应是指在输入信号为单位脉冲序列时&am…

酷开科技丨酷开系统大屏游戏新体验,夏日宅家娱乐新方案

随着夏日的临近&#xff0c;人们开始寻找各种方式来打发炎热天气中的空闲时间。不论是与朋友们聚会、追剧&#xff0c;还是与队友们一起沉浸在游戏中&#xff0c;酷开科技都能为你提供好的解决方案。如果你也渴望在家中享受激情四溢的游戏对战&#xff0c;那么酷开系统将是你的…

[Java基本语法] 继承与多态

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏:&#x1f355; Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm1001.2014.3001.5482 &#x1f9c0;线程与…

【LeetCode:2786. 访问数组中的位置使分数最大 + 递归 + 记忆化缓存 + dp】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

家用RJ45水晶头可以用在工业互联网中?

工业互联网作为智能制造的核心组成部分&#xff0c;已经在工业领域快速发展。在建立连接不同设备和系统的复杂网络中&#xff0c;网络设备和连接器的选择变得至关重要。其中&#xff0c;普遍使用的RJ45水晶头和网线在家庭和小型商业网络中被广泛采用&#xff0c;但是否适用于工…

Flink作业执行之 3.StreamGraph

Flink任务如何跑起来之 3.StreamGraph 1. StreamGraphGenerator 在前文了解Transformation和StreamOperator后。接下来Transformation将转换成StreamGraph&#xff0c;即作业的逻辑拓扑结构。 在env.execute()方法中调用getStreamGraph方法生成StreamGraph实例。StreamGraph…

如何在 ASP.NET Core Web Api 项目中应用 NLog 写日志?

前言 昨天分享了在 .NET Core Console 项目中应用 NLog 写日志的详细例子&#xff0c;有几位小伙伴私信说 ASP.NET Core Web Api 项目中无法使用&#xff0c;其实在 ASP.NET Core Web Api 项目中应用 NLog 写日志&#xff0c;跟 .NET Core Console 项目是有些不一样的&#xf…

如何确保数据跨域交换安全、合规、可追溯性?

数据跨域交换是指在不同的组织、系统或网络之间进行数据的传输和共享。随着数字经济的发展&#xff0c;数据跨域交换在促进数据流通和创新融合方面发挥着重要作用。然而&#xff0c;这一过程也面临着诸多挑战和风险&#xff0c;例如数据安全、合规性、完整性以及责任不清晰等问…

顶顶通呼叫中心中间件(mod_cti基于FreeSWITCH)-通话时长限制

文章目录 前言联系我们场景运用机器人场景普通通话场景 前言 顶顶通呼叫中心中间件限制通话时长有两种写法&#xff0c;分别作用于机器人场景与普通通话场景。 普通场景可分为分机互打、分机外呼手机等。 联系我们 有意向了解呼叫中心中间件的用户&#xff0c;可以点击该链接…

SAP Build 2-PDF数据提取与决策

0. 安装desktop agent 在后续过程中发现要预先安装desktop agent&#xff0c;否则没法运行自动化流程… 0.1 agent下载 参考官方文档说明 https://help.sap.com/docs/build-process-automation/sap-build-process-automation/create-user-in-rbsc-download-repository?loca…

RabbitMQ安装配置,封装工具类,发送消息及监听

1. Get-Started docker安装rabbitmq 拉取镜像 [rootheima ~]# docker pull rabbitmq:3.8-management 3.8-management: Pulling from library/rabbitmq 7b1a6ab2e44d: Pull complete 37f453d83d8f: Pull complete e64e769bc4fd: Pull complete c288a913222f: Pull complet…

C# Winform Chart图表使用和详解

Chart控件是微软自带的一种图形可视化组件&#xff0c;能展示种类丰富的图表形式。如曲线图&#xff0c;折线图&#xff0c;饼状图&#xff0c;环形图&#xff0c;柱状图&#xff0c;曲线面积图。 实例代码链接&#xff1a;https://download.csdn.net/download/lvxingzhe3/8943…

【wiki知识库】06.文档管理接口的实现--SpringBoot后端部分

目录 一、&#x1f525;今日目标 二、&#x1f388;SpringBoot部分类的添加 1.调用MybatisGenerator 2.添加DocSaveParam 3.添加DocQueryVo 三、&#x1f686;后端新增接口 3.1添加DocController 3.1.1 /all/{ebokId} 3.1.2 /doc/save 3.1.3 /doc/delete/{idStr} …

[Qt] Qt Creator 以及 Qt 在线安装教程

一、Qt Creator 下载及安装 1、从以下镜像源下载安装包常规安装即可 Qt Creator 也可以在第二步Qt 在线安装时一次性勾选安装&#xff0c;见后文 Qt Creator 中科大源下载地址 二、Qt 在线安装 1、根据所在平台选择对应的安装器下载 Qt 在线安装器下载 2、可能的安装报错…

云电脑有多好用?适合哪些人使用?

云电脑作为一种新型的计算模式&#xff0c;其应用场景广泛且多样&#xff0c;适合各类人群使用。云电脑适合什么人群使用&#xff1f;云电脑有哪些应用场景&#xff1f;有什么好的云电脑推荐&#xff1f;以下本文将详细探讨云电脑的主要应用场景及其适用人群的相关内容&#xf…

禁用PS/Photoshop等一系列Adobe旗下软件联网外传用户数据操作

方案一&#xff1a; 下载火绒杀毒&#xff0c;在联网请求上禁用Adobe软件的联网请求&#xff0c;甚至还可以额外发现哪些是它要想要偷偷摸摸干的。 方案二&#xff1a; 最后注意&#xff1a; 用盗版软件只是获得了使用权&#xff01;