简 述: Qt
的信号和槽原理分析:手写一个 moc 预编译器模拟生成 mo_xxx.cpp 过程
[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
背景
最近手工模拟了 Qt 的信号和槽实现原理,用纯 C++ 实现来实现一个 connet 函数。我的 💻 环境为: uos20 amd64
📎 Qt 5.11.3
📎 gcc/g++ 9.0
📎 gdb8.0
。
Object::db_connet(obj1, "sig1()", obj2, "slot1()");
原理
Qt 的信号和槽解决了 GUI 开发过程中 “对象间通信和共享数据” 问题。属于 Qt 的一个独创解决思路。关于为啥不用 C++ 的标准智能指针或者其它库呢??那时候 Boost 和 stl 都没诞生呢。所以代价就是通过 moc 预编译器来扩展 C++ 语法。
模拟 object.h 通过 moc 生成 db_object.cpp 的过程中,要想实现如下的一个 connet 函数,很重要就是解决 “A 对象的 n 信号,映射关联到 B 对象的 m 槽函数” 。
static void db_connet(Object *sender, const char *sig, Object *receiver, const char *slot);
object.h
下图 的object.h
模拟 QObject 类;
- 自定义 C++ 之外的关键字
db_signals
、db_slots
、db_emit
;编译器不报错的?添加为宏预定义处理即可。 MetaObject
对象模拟是元对象,用来在object
中用来存储db_signals
和db_slots
标记下面的函数名;对于信号,只需要声明,则不需要定义(通过 moc 自动在db_object.cpp 中生成 ),但是槽函数则需要自己在其它函数中声明和定义Connection
是做作为 ConnectionMap 的 value 使用的。保存本对象所链接的对象和对应的槽函数ConnectionMap
“对象 + 信号 ——–映射——> 对象 + 槽” 在代码中存储表现形式。ConnectionMapIt
是 ConnectionMap 遍历的游标。static MetaObject meta
具体的元对象。metacall
通过槽的索引 ==> 槽函数,然后调用static void db_connet()
静态函数设计,可在任何地方都可以使用,连接信号和槽函数void testSignal()
测试信号函数,看槽函数是否会成功被调用,即 emit signal( )
#ifndef OBJECT_H
#define OBJECT_H
#include <iostream>
#include <map>
using namespace std;
#define db_signals protected
#define db_slots
#define db_emit
class Object;
struct MetaObject // 元对象
{
const char *sig_names;
const char *slot_names;
static void active(Object *sender, int idx);
};
struct Connection
{
Object *recviver;
int method;
};
typedef multimap<int, Connection> ConnectionMap;
typedef multimap<int, Connection>::iterator ConnectionMapIt;
class Object
{
static MetaObject meta;
void metacall(int idx);
public:
static void db_connet(Object *sender, const char *sig, Object *receiver, const char *slot);
void testSignal();
public:
Object();
virtual ~Object();
db_signals:
void sig1();
// void sig2();
public db_slots:
void slot1();
// void slot2();
friend class MetaObject;
private:
ConnectionMap connectionsMap;
};
#endif // OBJECT_H
object.cpp
object 类的实现细节
#include "object.h"
#include <cstring>
static int findSignalIndex(const char *str, const char *subStr)
{
if (!str || !subStr || strlen(str) < strlen(subStr))
return -1;
int ret = strcmp(str, subStr);
if (ret == 0)
return ret;
else
return -1;
}
void Object::db_connet(Object *sender, const char *sig, Object *receiver, const char *slot)
{
int sig_idx = findSignalIndex(sender->meta.sig_names, sig);
int slot_idx = findSignalIndex(receiver->meta.slot_names, slot);
if (sig_idx == -1 || slot_idx == -1) {
cout<<"signal or slot not found!";
return;
} else {
Connection c = {receiver, slot_idx};
sender->connectionsMap.insert(pair<int, Connection>(sig_idx, c)); // connectionsMap 私有成员
}
}
void Object::testSignal()
{
db_emit sig1();
}
Object::Object()
{
}
Object::~Object()
{
}
// 通过 sender 的信号 idx ==> 槽函数
void MetaObject::active(Object *sender, int idx)
{
pair<ConnectionMapIt, ConnectionMapIt> ret;
ret = sender->connectionsMap.equal_range(idx); // 寻找[idx, )
for (ConnectionMapIt it = ret.first; it != ret.second; ++it) {
Connection c = (*it).second;
c.recviver->metacall(idx);
}
}
db_object.cpp
object 类通过 moc 生成 db_object.cpp,一些 moc 所执行的代码扩展在 db_object.cpp 里面。
#include "object.h"
//db_object: 是由 moc 编译器 将 object.cpp 展开的内容(此处手写表示)
const char sig_names[] = "sig1()";
const char slot_names[] = "slot1()";
MetaObject Object::meta = {sig_names, slot_names};
void Object::sig1()
{
MetaObject::active(this, 0);
}
void Object::slot1()
{
cout << "-----------> this is slot1()";
}
// 槽的索引==> 槽函数
void Object::metacall(int idx)
{
switch (idx) {
case 0:
slot1();
break;
default:
break;
}
}
main.cpp
#include <iostream>
#include "object.h"
using namespace std;
// 目的:自行构造 moc 编译器,手动将 object.h --> db_bject.cpp (宏 和 moc 编译器处理的部分)
// 时间:2021-03-26
// 下载:https://github.com/xmuli/QtExamples
int main(int argc, char *argv[])
{
Object *obj1 = new Object();
Object *obj2 = new Object();
Object::db_connet(obj1, "sig1()", obj2, "slot1()");
obj1->testSignal();
return 0;
}
// 终端输出打印:
----------> this is slot1()
总结
本文参考 文一、 文二 ,但实际发现其源码之间有几处错误,实际运行失败,参考思路重写,方便后来者学习,且本文还有改进之处,日后有空改进,修改点如下:
findSignalIndex
函数重写,使得能够识别信号和槽函数的重载函db_connet
在重构,支持宏方式(Qt4)和 函数指针(Qt5)方式;(当前为使用字符串作为参数)- 构建 xxx_p.h,将成员变量都放在此文件中,加快项目的编译速度。
系列地址
QtExamples 【DbSigSlot】
欢迎 star
和 fork
这个系列的 QT / DTK 学习,附学习由浅入深的目录。