时间乃是最大的革新家——培根


先了解一下相关宏:

qt为c++增加的相关宏:signals, slots,emit

在qt的预编译过程中,这些宏会被替换。

1)#define signals public  

Qt4中为 protected. Qt5为支持connect函数指针写法, 定义为 public.

2)#define slots

slots被替换成空! 其作用仅仅是给qt的用户进行提示。

3)#define SLOT(a) "1"#a

     #define SIGNAL(a) "2"#a

连接信号槽使用的宏 (Qt4 的写法, 在函数名前添加数字, 用于标识[2]信号, [1]槽函数, [0]普通方法。)

4)#define emit

emit被替换成空, 其作用仅仅是给qt的用户进行提示。

每个继承了 QObject, 并使用了 Q_OBJECT 的类,被moc 预编译后, 会生成 moc_xxx.cpp 文件,(xxx为类的名字) 。在该文件中有该类的内省表, 内省表记录了索引值 与各函数-信号-槽函数之间的对应关系(每个函数、信号、槽都有一个索引)。 在Qt中都是使用索引值来作为参数传递函数的。

每个类中的每个信号, 都维护了一个 ConnectionList, 用于存储连接到其上的目标函数。

在 connect 时, 需要提供 sender, signal func, receiver, call func. (当然还有连接方式, 不过和此处要讲的流程关系不大). connect 函数将 receiver 和 call func 打包成一个 Connection 节点, 并存储在 sender 的 singnal func 对应的 ConnectionList 中.

在 emit signal 时, 调用了QMetaObject::activate(), 该方法会根据传入的 sender 和 singnal func 对应的索引, 找到 ConnectionList. 并回调这些方法。

Qt5新增了允许将普通函数, lambda等连接到信号上。

Tips: 因为 receiver 在析构时, 需要 disconnect 所有已连接的信号. 所以每个 receiver 也应该维护一个列表, 用于保存已连接的 sender 和 signal.


自己实现信号槽的连接

用C++来实现自己的元对象系统。参考了网友的https://blog.csdn.net/fuyunzhishang1/article/details/48345381

我的例子并不是严格按照qt中的逻辑:有一个QObject类,然后其他类继承自这个类。这里只是定义了一个类,主要是为了理解下信号和槽是如何连接起来的。

思路比较简易:先将信号和槽的名字收集起来,然后根据用户写的connect函数将配对的信号和槽利用一系列的数据结构对应起来。

在我的例子中:定义了Object类,类中有成员MetaObject meta;该成员的作用是存储Object类的所有信号的名字,槽的名字。

在main.cpp中有:

obj1.meta.sig_names = "sig1";  //This should be done by moc in QT.

obj2.meta.slts_names = "slot1";

这个收集名字的工作在qt中是由moc来完成的,这里手工模仿了。例如,信号名sig1,其中的1代表信号的序号,slot1中1代表槽函数的序号。Object::metacall(int idx)函数根据槽函数的序号来进行响应的调用。

运行一遍代码就可以明白:

类定义文件 Object.h

#ifndef OBJECT_H
#define OBJECT_H #include <map> //宏定义,防止c++编译器不识别:---所谓的信号或槽,都只是普普通通的C++类的成员函数
# define db_slots
# define db_signals protected
# define db_emit class Object; struct MetaObject //定义一个类,来存放信息. 将该类作为Object的静态成员。
{
const char * sig_names;
const char * slts_names;
static void active(Object * sender, int idx);
};
struct Connection
{
Object * receiver;
int method;
}; typedef std::multimap<int, Connection> ConnectionMap;
typedef std::multimap<int, Connection>::iterator ConnectionMapIt; class Object //对标qt的QObject类
{
public:
MetaObject meta;
void metacall(int idx); //该函数的实现在db_Object.cpp中,根据索引找到槽函数并调用之 Object();
virtual ~Object();
static int find_string(const char * str, const char * substr); //通过 db_connect函数 存储信号接受者以及其槽函数的索引 到一个 ConnectionMap(private变量:connections)。!!!!
// 存储信号接受者以及其槽函数的索引 构成一个Connection类型,作为ConnectionMap的一个键值对中的值,
//相应的键为 信号函数在信号发射者内部的索引
static void db_connect(Object*, const char*, Object*, const char*);
void testSignal(); // 为了和普通成员进行区别(以使得预处理器可以知道如何提取信息),创造一些"关键字":db_signals, db_slots,对标qt的signals和slots. db_signals:
void sig1(); //qt的QObject类里没有这个,这里纯粹是为了测试。 public db_slots:
void slot1(); friend class MetaObject; private:
ConnectionMap connections; }; #endif // OBJECT_H

类实现文件 Object.cpp:

#include "Object.h"
#include <stdio.h>
#include <string.h> void MetaObject::active(Object* sender, int idx)
{
ConnectionMapIt it;
std::pair<ConnectionMapIt, ConnectionMapIt> ret;
ret = sender->connections.equal_range(idx);
for (it=ret.first; it!=ret.second; ++it) //调用所有的与该信号相链接的 槽函数(这些槽函数可能属于不同对象)
{
Connection c = (*it).second;
c.receiver->metacall(c.method);
}
} ///////////////////////////////////// Object::Object()
{ } Object::~Object()
{
} int Object::find_string(const char * str, const char * substr)
{
std::string str_s = str;
std::string substr_s = substr;
if(str_s.find(substr_s) >=0)
{
std::string temp = substr_s.substr(substr_s.length()-1,1);
int a = atoi(temp.c_str()); return a;
} } //首先从元对象信息中查找信号和槽的名字是否存在,如果存在,
//则将信号的索引和接收者的信息存入 信号发送者 的一个map中。如果信号或槽无效,就什么都不用做了。
void Object::db_connect(Object* sender, const char* sig, Object* receiver, const char* slt)
{
int sig_idx = Object::find_string(sender->meta.sig_names, sig);
int slt_idx = Object::find_string(receiver->meta.slts_names, slt);
if (sig_idx == -1 || slt_idx == -1) {
perror("signal or slot not found!");
} else {
Connection c = {receiver, slt_idx}; //存储信号接受者以及其槽函数的索引
sender->connections.insert(std::pair<int, Connection>(sig_idx, c));
}
} void Object::slot1()
{
printf("hello hhhhhh!");
} void Object::metacall(int idx)
{
switch (idx) {
case 1:
slot1();
break;
default:
break;
};
}
void Object::testSignal()
{
db_emit sig1();
}
void Object::sig1()
{
MetaObject::active(this, 1);
}

main.cpp

#include <iostream>
#include "Object.h"
using namespace std; int main()
{
Object obj1, obj2; obj1.meta.sig_names = "sig1"; //This should be done by moc in QT.
obj2.meta.slts_names = "slot1"; Object::db_connect(&obj1, "sig1", &obj2, "slot1");
obj1.testSignal();
return 0;
}

Qt信号-槽原理剖析--(2)自己实现信号槽的更多相关文章

  1. Qt信号-槽原理剖析--(1)信号槽简介

    唯有创造才是快乐.只有创造的生灵才是生灵.--罗曼·罗兰 信号槽是观察者模式的一种实现,特性如下: A.一个信号就是一个能够被观察的事件,或者至少是事件已经发生的一种通知: B.一个槽就是一个观察者, ...

  2. Qt信号槽-原理分析

    目录 一.问题 二.Moc 1.变量 2.Q_OBJECT展开后的函数声明 3.自定义信号 三.connect 四.信号触发 1.直连 2.队列连接 五.总结 六.推荐阅读 一.问题 学习Qt有一段时 ...

  3. PyQt(Python+Qt)学习随笔:使用pyqtConfigure建立信号和槽的连接

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 在PyQt中,一般信号和槽的连接是通过connect方法建立的,语法如下: connect(slot ...

  4. QT从入门到入土(三)——信号和槽机制

    摘要 信号槽是 Qt 框架引以为豪的机制之一.所谓信号槽,实际就是观察者模式.当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号 (signal).这种发出是没有目的的,类似广播 ...

  5. PyQt Designer中带参数的信号为什么匹配不到带参数的槽函数?

    老猿在学习ListView组件时,想实现一个在ListView组件中选中一个选择项后触发消息给主窗口,通过主窗口显示当前选中的项的内容. 进入QtDesigner后,设计一个图形界面,其中窗口界面使用 ...

  6. JVM Attach实现原理剖析

    本文转载自JVM Attach实现原理剖析 前言 本文旨在从理论上分析JVM 在 Linux 环境下 Attach 操作的前因后果,以及 JVM 为此而设计并实现的解决方案,通过本文,我希望能够讲述清 ...

  7. 从源码剖析Go语言基于信号抢占式调度

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com/archives/485 本文使用的go的源码15.7 这一次来讲讲基于信号式抢占式调度 ...

  8. DelayQueue延迟队列原理剖析

    DelayQueue延迟队列原理剖析 介绍 DelayQueue队列是一个延迟队列,DelayQueue中存放的元素必须实现Delayed接口的元素,实现接口后相当于是每个元素都有个过期时间,当队列进 ...

  9. ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件)

    ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件) Startup Class 1.Startup Constructor(构造函数) 2.Configure ...

随机推荐

  1. Vue --- 项目创建

    目录 创建Vue项目之前的准备 创建Vue项目 重新构建项目 项目目录介绍 项目的生命周期 Vue文件式组件 配置自定义全局样式 路由逻辑跳转 生命周期钩子 路由传参的两种方式 创建Vue项目之前的准 ...

  2. 我永远讨厌gch文件

    一个学期没写博客了. 今天写OOP作业见鬼了, 调了半天. 我写了一个match.h和一个match.cpp, 然后match.cpp里面#include"match.h", 然后 ...

  3. [TJOI2015]概率论——期望&&母函数

    题意 求一个含有 $n$ 个结点的有序二叉树的叶子节点的期望个数.($n \leq 10^9$) 分析 一堆推导..... 得 $ans = \frac{n(n+1)}{2(2n-1)}$ #incl ...

  4. [51Nod 1220] - 约数之和 (杜教筛)

    题面 令d(n)d(n)d(n)表示nnn的约数之和求 ∑i=1n∑j=1nd(ij)\large\sum_{i=1}^n\sum_{j=1}^nd(ij)i=1∑n​j=1∑n​d(ij) 题目分析 ...

  5. Windbg命令脚本流程控制语句详解

    在Windbg命令脚本一文里,我们介绍了命令脚本语言的的组成要素,在本文里将对语句进行展开的讲解.这些语句主要是流程控制的语句,比如我们常见的条件分子和循环语句等. ; (命令分隔符) 分号(:)字符 ...

  6. chmod/chown/chgrp/chattr

    权限组合其实就是二进制的组合 注意,用户只能修改属于自己的文件 仅管理员可以修改文件的数组和属主 chmod 三种使用方法 文件的特殊权限 SUID SGID Sticky chowm 修改文件,目录 ...

  7. avalon $computed不起作用?

    computed写的个数,应该是只能一个的,之前写了两个,一个是空,一个里面有数据,那个有数据的不起作用,但是在有数据的里面写一个一个console.log就会起作用,所以将多余的空的computed ...

  8. D3.js的v5版本入门教程(第十三章)—— 饼状图

    D3.js的v5版本入门教程(第十三章) 这一章我们来绘制一个简单的饼状图,我们只绘制构成饼状图基本的元素——扇形.文字,从这一章开始,内容可能有点难理解,因为每一章都会引入比较多的难理解知识点,在这 ...

  9. 《JAVA程序设计》_第十周学习总结

    一.学习内容 12.1进程与进程 程序是一段静态的代码,进程是程序的一次动态执行过程,这个过程也是进程本身从产生.发展至消亡的过程. 线程不是进程,是比进程更小的执行单位.但与进程不同的是,线程的中断 ...

  10. XmlIgnore的解释和使用

    XmlIgnore是一个自定义属性,用来指明在序列化时是否序列化一个属性.如下面的例子: public class Group { public string GroupName; [XmlIgnor ...