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;笔刷不会覆盖之前…

网安要求关闭所有系统标签页后,自动去除登录人信息(包括直接关闭整个浏览器)

暂时没找到什么优美得解决办法&#xff0c;如遇到&#xff0c;请留言&#xff0c;谢谢浏览器没有关闭事件&#xff0c;只有在关闭时会调用beforeunload&#xff0c;unload&#xff0c;但是再刷新时会调用beforeunload&#xff0c;unload&#xff0c;onload 因此关闭再打开与刷新…

每日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 …

[Day 10] 區塊鏈與人工智能的聯動應用:理論、技術與實踐

AI在各行業的應用實例 人工智能&#xff08;AI&#xff09;作為當今最具影響力的技術之一&#xff0c;已經在各行業中展現出廣泛的應用前景。從金融到醫療、零售到製造&#xff0c;AI正在改變各行業的運營模式、提升效率、降低成本並創造新的機會。本文將深入探討AI在幾個主要…

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…

OPPO-HR面笔记

HR面 自我介绍&#xff1a; 尊敬的面试官&#xff0c;您好&#xff01;非常荣幸能够拥有这次机会。我叫周俊&#xff0c;来自西南大学信息管理与信息系统专业&#xff0c;目前是一名准大三学生。&#xff1a; 第一&#xff0c;在自主学习能力方面&#xff0c;我每日都在Android…

如何在 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;例如数据安全、合规性、完整性以及责任不清晰等问…

elasticsearch快照生成与恢复

Elasticsearch快照生成与恢复的场景主要涉及到数据的备份与恢复需求。当需要对Elasticsearch集群中的数据进行备份&#xff0c;或者在数据丢失、损坏等情况下需要恢复数据时&#xff0c;就可以使用快照功能。 快照生成的方法通常包括以下步骤&#xff1a; 1、创建一个快照仓库…

Day41 - Day45

Day41 - Day45 Day41&#xff08;2000年Text4&#xff09; With economic growth has come centralization; fully 76 percent of Japan’s 119 million citizens live in cities where community and the extended family have been abandoned in favor of isolated, two-gen…

opencv中的图像操作

图像操作 输入输出 从文件中加载图像&#xff1a; Mat img imread(filename); // 读取灰度图Mat img imread(filename, IMREAD_GRAYSCALE);保存图像&#xff1a; imwrite(filename,img);文件的格式由其扩展名决定。 使用cv::imdecode和cv::imencode从/到内存读取和写入图像…

Web前端、后端与建站:全方位解析四大基石、五大挑战、六大技术与七大策略

Web前端、后端与建站&#xff1a;全方位解析四大基石、五大挑战、六大技术与七大策略 在当今数字化时代&#xff0c;Web前端、后端以及建站技术已经成为构建现代网站和应用不可或缺的关键要素。本文将从四个方面探讨Web前端与后端的基础&#xff0c;从五个方面分析建站过程中的…

docker镜像是什么意思,如何通俗点的解释一下

Docker镜像&#xff08;Docker Image&#xff09;可以通俗地理解为一个包含应用程序及其运行环境的“模版”或“快照”。这些镜像是应用程序在Docker容器中运行的基础。以下是一个更详细的解释&#xff1a; 通俗解释 软件快照&#xff1a;想象你有一个操作系统、几个软件和一些…