qt qbytearray 将接收数据存进数组_Qt原理-窥探信号槽的实现细节
目录(放个目录方便预览。这个目录是从博客复制过来的,点击会跳转到博客)简介猫和老鼠的故事声明与实现Q_OBJECT宏信号的moc生成信号的触发槽和moc生成第三方信号槽实现 简介本文是《Qt进阶之路》系列文章的特别篇,涛哥在这里讨论Qt信号-槽的实现细节。上次的文章《Qt实用技能4-认清信号槽的本质》中介绍过,信号-槽是一种对象之间的通信机制,是Qt在标准C++之外,使用元对象编译器(MOC)实现
目录
(放个目录方便预览。这个目录是从博客复制过来的,点击会跳转到博客)
- 简介
- 猫和老鼠的故事
- 声明与实现
- Q_OBJECT宏
- 信号的moc生成
- 信号的触发
- 槽和moc生成
- 第三方信号槽实现
简介
本文是《Qt进阶之路》系列文章的特别篇,涛哥在这里讨论Qt信号-槽的实现细节。
上次的文章《Qt实用技能4-认清信号槽的本质》中介绍过,信号-槽是一种对象之间的
通信机制,是Qt在标准C++之外,使用元对象编译器(MOC)实现的语法糖。
这次通过一个简单的案例,学习一些信号-槽的实现细节。
猫和老鼠的故事
还是拿上次的设定来说明:Tom有个技能叫”喵”,就是发出猫叫,而正在偷吃东西的Jerry,听见猫叫声就会逃跑。
我们用信号-槽的方式写出来。
//Tom.h
//Jerry.h
以上面的代码为例,要使用信号-槽功能,先决条件是继承QObject类,并在类声明中增加Q_OBJECT宏。
之后在”signals:” 字段之后声明一些函数,这些函数就是信号。
在”public slots:” 之后声明的函数,就是槽函数。
接下来看看我们的main函数:
//main.cpp
信号-槽都准备好了,接下来创建两个对象实例,并使用QObject::connect将信号和槽连接起来。
最后使用emit发送信号,就会自动触发槽函数了。
运行结果:
声明与实现
信号和槽的本质都是函数。
我们知道C++中的函数要有声明(declare),也要有实现(implement),
而信号只要声明,不需要写实现。这是因为moc会为我们自动生成。
另外触发信号时,不写emit关键字,直接调用信号函数,也是没有问题的。
这是因为emit是一个空的宏
#define emit
Q_OBJECT宏
我们来看一下Q_OBJECT宏,展开如下:
(不同的Qt版本有些差异,涛哥这里用的是5.12.4,以此为例)
public
我们看到,关键的地方,是声明了一个只读的静态成员变量staticMetaObject,以及3个public的成员函数
static
还有一个private的静态成员函数qt_static_metacall
static
那么声明的这些成员变量/函数,在哪里实现?答案是moc生成的cpp文件。
信号的moc生成
如上图所示目录结构,项目编译完成后,在build文件夹中,自动生成了moc_Jerry.cpp 和 moc_Tom.cpp两个文件
其中moc_Tom.cpp内容如下:
/****************************************************************************
可以大致看出,生成的cpp文件中,就是变量staticMetaObject以及 那几个函数的实现。
staticMetaObject是一个结构体,用来存储Tom这个类的信号、槽等元信息,并把
qt_static_metacall静态函数作为函数指针存储起来。
因为是静态成员,所以实例化多少个Tom对象,它们的元信息都是一样的。
qt_static_metacall函数提供了两种“元调用的实现”:
如果是InvokeMetaMethod类型的调用,则直接 把参数中的QObject对象,
转换成Tom类然后调用其miao函数
如果是IndexOfMethod类型的调用,即获取元函数的索引号,则计算miao函数的偏移并返回。
而moc_Tom.cpp末尾的
// SIGNAL 0
就是信号函数的实现。
信号的触发
miao信号的实现,直接调用了QMetaObject::activate函数。其中0代表miao这个函数的索引号。
QMetaObject::activate函数的实现,在Qt源码的QObject.cpp文件中,略微复杂一些,且不同版本的Qt,实现差异都比较大,
这里总结一下大致的实现:
先找出与当前信号连接的所有对象-槽函数,再逐个处理。
这里处理的方式,分为三种:
if
receiverInSameThread表示当前线程id和接收信号的对象的所在线程id是否相等。
如果信号-槽连接方式为QueuedConnection,不论是否在同一个线程,按队列处理。
如果信号-槽连接方式为Auto,且不在同一个线程,也按队列处理。
如果信号-槽连接方式为阻塞队列BlockingQueuedConnection,按阻塞处理。
(注意同一个线程就不要按阻塞队列调用了。因为同一个线程,同时只能做一件事,本身就是阻塞的,直接调用就好了,
如果走阻塞队列,则多了加锁的过程。如果槽中又发了同样的信号,就会出现死锁:加锁之后还未解锁,又来申请加锁。)
队列处理,就是把槽函数的调用,转化成了QMetaCallEvent事件,通过QCoreApplication::postEvent放进了事件循环。
等到下一次事件分发,相应的线程才会去调用槽函数。
关于事件循环,可以参考之前的文章《Qt实用技能3-理解事件循环》
槽和moc生成
slot函数我们自己实现了,moc不会做额外的处理,所以自动生成的moc_Jerry.cpp文件中,只有Q_OBJECT宏的展开,
和前面的moc_Tom.cpp是一致的,不赘述了。
第三方信号槽实现
信号-槽是非常优秀的通信机制,但Qt的moc实现方式,被一些人诟病,所以他们造了新的轮子,比如:
https://woboq.com/blog/verdigris-qt-without-moc.html
http://sigslot.sourceforge.net/
https://github.com/NoAvailableAlias/nano-signal-slot
https://github.com/pbhogan/Signals
相关链接
涛哥的博客:https://jaredtao.github.io
武威涛哥的博客jaredtao.github.io涛哥的博客-国内镜像: https://jaredtao.gitee.io
https://jaredtao.gitee.iojaredtao.gitee.ioQt进阶之路- 高质量交流 TIM群/QQ群: 734623697
更多推荐


所有评论(0)