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 MyEmit

class 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;
	else
		return 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++; //去掉前面的 2
	member++; //去掉前面的 1
	MetaData* 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

代理模式与静态代理、动态代理的实现(Proxy.newProxyInstance、InvocationHandler)

代理模式 代理模式是23种设计模式中比较常用的一种&#xff0c;属于结构型设计模式。在 Android 领域中&#xff0c;有大量的库都使用了代理模式&#xff0c;例如 Retrofit 使用动态代理来实现 API 接口的调用&#xff0c;Dagger 使用代码生成和反射机制来创建依赖注入的代理对…

Linux——ansible剧本

剧本&#xff08;playbook&#xff09; 现在&#xff0c;可以写各种临时命令 但如果&#xff0c;想把所有步骤&#xff0c;集合到一起&#xff0c;写到同一个文件里 让ansible自动按顺序执行 就必须要写“剧本” 剧本里面&#xff0c;也可以写临时命令&#xff0c;但是剧本…

大数据集成平台建设方案(Word方案)

基础支撑平台主要承担系统总体架构与各个应用子系统的交互&#xff0c;第三方系统与总体架构的交互。需要满足内部业务在该平台的基础上&#xff0c;实现平台对于子系统的可扩展性。基于以上分析对基础支撑平台&#xff0c;提出了以下要求&#xff1a; 基于平台的基础架构&…

自动驾驶---Perception之视觉点云雷达点云

1 前言 在自动驾驶领域&#xff0c;点云技术的发展历程可以追溯到自动驾驶技术的早期阶段&#xff0c;特别是在环境感知和地图构建方面。 在自动驾驶技术的早期技术研究中&#xff0c;视觉点云和和雷达点云都有出现。20世纪60年代&#xff0c;美国MIT的Roberts从2D图像中提取3D…

荣耀手机删除系统APP

1、打开开发者模式 设置–系统–关于手机–快速多次点击手机的版本号&#xff0c;即可进入开发者模式。 然后进入开发人员选项&#xff0c;开启USB调试&#xff0c;如下图。 2、数据线连接电脑&#xff0c;检查设备连接情况 按键盘winR键&#xff0c;在弹窗中输入cmd&#…

idea有这个类却报红,无法用快捷键找到

idea有这个类却报红&#xff0c;无法用快捷键找到&#xff0c;但是项目启动却没有任何问题&#xff0c;严重影响到了开发效率&#xff0c;关idea 重新打开没有用。 找了一圈&#xff0c;办法如下&#xff1a; 1、点击左上角的 File—>Invalidate Caches/Restar 2、点击 In…

PostgreSQL和Oracle的数据类型对比:时间类型 #PG培训

在数据库管理系统中&#xff0c;时间数据类型是非常关键的一部分。时间数据类型的选择和使用直接影响到数据存储、查询效率和应用程序的设计。本文将对比PostgreSQL和Oracle在时间类型方面的实现和特性。 #PG考试#postgresql培训#postgresql考试#postgresql认证 日期和时间类型…

MFC基础学习应用

MFC基础学习应用 1.基于对话框的使用 左上角为菜单键&#xff08;其下的关于MFC主要功能由IDD_ABOUTBOX决定) 附图 右下角为按钮&#xff08;基本功能由IDD_DIALOG决定,添加按钮使用由左上角的工具箱完成) 附图 2.自行添加功能与按钮//功能代码 void CMFCApplication4Dlg:…

渗透测试练习题解析 6 (CTF web)

1、[HCTF 2018]admin 1 考点&#xff1a;二次注入 先注册一个账号看看&#xff0c;注册 admin 会提示该用户名已存在&#xff0c;那我们就换一个用户名 观察页面功能&#xff0c;存在一个修改密码&#xff0c;开始的思路是想看看能否通过该密码功能抓包然后修改用户名为 admin …

react用ECharts实现组织架构图

找到ECharts中路径图。 然后开始爆改。 <div id{org- name} style{{ width: 100%, height: 650, display: flex, justifyContent: center }}></div> // data的数据格式 interface ChartData {name: string;value: number;children: ChartData[]; } const treeDep…

数据结构与算法笔记:基础篇 -图的表示:如何存储微博、微信等社交网络中的好友关系?

概述 微博、微信这些社交软件你肯定玩过吧。在微博中&#xff0c;两个人可以互相关注&#xff1b;在微信中&#xff0c;两个人可以互加好友。那你知道&#xff0c;如何存储微博、微信等这些社交网络的好友关系吗&#xff1f; 这就用到本章讲解的这种数据结构&#xff1a;图。…

watch什么场景下会被影响?

❌1、当组件通过import这种方式&#xff0c;子组件watch由于加载慢&#xff0c;不会被执行 ❌定位上发问题 1、当前页面刷新&#xff0c;以为是watch绑定的值没有改变&#xff0c;通过workflowId null 改变&#xff0c;子组件还是不会触发watch&#xff0c; 2、 当前页面刷新…

【Linux文件篇】磁盘到用户空间:Linux文件系统架构全景

W...Y的主页 &#x1f60a; 代码仓库分享 &#x1f495; 前言&#xff1a;我们前面的博客中一直提到的是被进程打开的文件&#xff0c;而系统中不仅仅只有被打开的文件还有很多没被打开的文件。如果没有被打开&#xff0c;那么文件是在哪里进行保存的呢?那我们又如何快速定位…

opencv安装笔记 各种平台

目录 python安装opencv-python c 麒麟arm系统安装和用法 python安装opencv-python pypi上搜索 Search results PyPI 现在安装是一个版本&#xff0c;大于3.6都可以安装 c 麒麟arm系统安装和用法 参考&#xff1a; ffmpeg rknn麒麟系统 安装 opencv_ffmpeg4 解码示例-CSDN…

达梦数据库上市,给数据库国产化加油打气

吉祥学安全知识星球&#x1f517;除了包含技术干货&#xff1a;《Java代码审计》《Web安全》《应急响应》《护网资料库》《网安面试指南》还包含了安全中常见的售前护网案例、售前方案、ppt等&#xff0c;同时也有面向学生的网络安全面试、护网面试等。 作为家乡的企业上市必须…

【等保资料】等级保护定级指南及网络安全解读(ppt原件)

新版网络安全等级保护定级指南网络安全等级保护工作的作用对象&#xff0c;主要包括基础信息网络、工业控制系统、云计算平台、物联网、使用移动互联技术的网络和大数据等。 软件全套精华资料包清单部分文件列表&#xff1a; 工作安排任务书&#xff0c;可行性分析报告&#xf…

新版eclipseSpringToolSuite4 get set方法自动生成注释(适用2019-03之后版本)

今天分享一个新版本eclipse 自动生成getter/setter文档注释的方法&#xff0c;看一下效果图 public class Test {//姓名private String name;/** * 获取 姓名 * return name 姓名 */public String getName() {return name;}/** * 设置 姓名 * param name 姓名 */public void …

Databricks超10亿美元收购Tabular;Zilliz 推出 Milvus Lite ; 腾讯云支持Redis 7.0

重要更新 1. Databricks超10亿美元收购Tabular&#xff0c;Databricks将增强 Delta Lake 和 Iceberg 社区合作&#xff0c;以实现 Lakehouse 底层格式的开放与兼容([1] [2])。 2. Zilliz 推出 Milvus Lite 轻量级向量数据库&#xff0c;支持本地运行&#xff1b;Milvus Lite 复…

人工智能在数字病理领域的最新进展|顶刊速递·24-06-14

小罗碎碎念 文献主题&#xff1a;人工智能在【数字病理】领域的最新进展 今天在写这篇推文的时候&#xff0c;脑子里就一个念头——大模型的风&#xff0c;终于还是卷到了病理学领域。 这是一个好事哈&#xff0c;如果我们搞病理研究的&#xff0c;也能有一个像Chatgpt一样的工…

【推荐算法】精排模型总结

文章目录 1、推荐算法的5个维度2、交叉结构2.1、LR回归 & FTRL优化算法2.2、FM&#xff1a;因子分解机&#xff0c;显式二阶交叉2.3、Wide & Deep&#xff1a;兼顾记忆与扩展&#xff0c;隐式交叉2.4、DeepFM&#xff1a;wide&deep基础上融合了二阶显式交叉的FM2.5…