用ISO C++实现自己的信号槽(Qt另类学习)

dbzhang800 2011-05-02 04:04:57
用ISO C++实现自己的信号槽(Qt另类学习)

本文使用 ISO C++ 一步一步实现了一个极度简化的信号与槽的系统 (整个程序4个文件共121行代码) 。希望能有助于刚进入Qt世界的C++用户理解Qt最核心的信号槽与元对象系统是如何工作的。

引入元对象系统

首先定义自己的信号和槽

为了和普通成员进行区别(以使得预处理器可以知道如何提取信息),我们需要创造一些"关键字"

db_signals
db_slots

class Object
{
public:
Object();
virtual ~Object();
db_signals:
void sig1();
public db_slots:
void slot1();
};


通过自己的预处理器,将信息提取取来,放置到一个单独的文件中(比如db_object.cpp):
规则很简单,将信号和槽的名字提取出来,放到字符串中。可以有多个信号或槽,按顺序"sig1\nsig2\n"

static const char sig_names[] = "sig1\n";
static const char slts_names[] = "slot1\n";


这些信号和槽的信息,如何才能与类建立关联,如何被访问呢?

我们可以定义一个类,来存放信息:

struct MetaObject
{
const char * sig_names;
const char * slts_names;
};

然后将其作为一个Object的静态成员(注意哦,这就是我们的元对象啦 ):

class Object
{
static MetaObject meta;
...


这样一来,我们的预处理器可以生成这样的 db_object.cpp 文件:

#include "object.h"

static const char sig_names[] = "sig1\n";
static const char slts_names[] = "slot1\n";
MetaObject Object::meta = {sig_names, slts_names};


信息提取的问题解决了:可是,还有一个严重问题,我们定义的关键字 C++ 编译器不认识啊,怎么办?

呵呵,好办,通过定义一下宏,问题是不是解决了:

    # define db_slots
# define db_signals protected


建立信号槽链接

我们的最终目的就是:当信号被触发的时候,能找到并触发相应的槽。所以有了信号和槽的信息,我们就可以建立信号和槽的连接了。我们通过 db_connect 将信号和槽的对应关系保存到一个 mutlimap 中:

struct Connection
{
Object * receiver;
int method;
};

class Object
{
public:
...
static void db_connect(Object*, const char*, Object*, const char*);
...
private:
std::multimap<int, Connection> connections;


上面应该不需要什么解释了,我们直接看看db_connect该怎么写:

void Object::db_connect(Object* sender, const char* sig, Object* receiver, const char* slt)
{
int sig_idx = find_string(sender->meta.sig_names, sig);
int slt_idx = 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));
}
}

首先从元对象信息中查找信号和槽的名字是否存在,如果存在,则将信号的索引和接收者的信息存入信号发送者的的一个map中。如果信号或槽无效,就什么都不用做了。

我们这儿定义了一个find_string函数,就是个简单的字符串查找(此处就不列出了)。

信号的激活


连接信息有了,我们看看信号到底是怎么发出的。

在 Qt 中,我们都知道用 emit 来发射信号:

class Object
{
public:
void testSignal()
...
};

void Object::testSignal()
{
db_emit sig1();
}

这儿 db_emit 是神马东西?C++编译器不认识啊,没关系,看仔细喽,加一行就行了

#define db_emit


从前面我的Object定义中可以看到,所谓的信号或槽,都只是普普通通的C++类的成员函数。既然是成员函数,就需要函数定义:

槽函数:由于它包含我们需要的功能代码,我们都会想到在 object.cpp 文件中去定义它,不存在问题。
信号函数:它的函数体不需要自己编写。那么它在哪儿呢?这就是本节的内容了

信号函数由我们的"预处理器"来生成,也就是它要定义在我们的 db_object.cpp 文件中:

void Object::sig1()
{
MetaObject::active(this, 0);
}

我们预处理源文件时,就知道它是第几个信号。所以根据它的索引去调用和它关联的槽即可。具体工作交给了MetaObject类:

class Object;
struct MetaObject
{
const char * sig_names;
const char * slts_names;

static void active(Object * sender, int idx);
};


这个函数该怎么写呢:思路很简单

从前面的保存连接的map中,找出与该信号关联的对象和槽
调用该对象这个槽

typedef std::multimap<int, Connection> ConnectionMap;
typedef std::multimap<int, Connection>::iterator ConnectionMapIt;

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);
}
}


槽的调用

这个最后一个关键问题了,槽函数如何根据一个索引值进行调用。

直接调用槽函数我们都知道了,就一个普通函数
可现在通过索引调用了,那么我们必须定义一个接口函数

class Object
{
void metacall(int idx);
...


该函数如何实现呢?这个又回到我们的元对象预处理过程中了,因为在预处理的过程,我们能将槽的索引和槽的调用关联起来。

所以,在预处理生成的文件(db_object.cpp)中,我们很容易生成其定义:

void Object::metacall(int idx)
{
switch (idx) {
case 0:
slot1();
break;
default:
break;
};
}


至此,我们已经实现的一个简化的自己的信号与槽的程序。下面我们总体上看看程序的所有代码:
...全文
482 12 打赏 收藏 转发到动态 举报
写回复
用AI写文章
12 条回复
切换为时间正序
请发表友善的回复…
发表回复
fengwanzk 2011-05-27
  • 打赏
  • 举报
回复
写的很不错。
TwilightSun 2011-05-27
  • 打赏
  • 举报
回复
最近想自己写一个简单的C++库,看了楼主的文章大受启发
dbzhang800 2011-05-27
  • 打赏
  • 举报
回复
[Quote=引用 9 楼 billyhe1983 的回复:]

用qt提供的QSignalMapper不是能很好解决问题吗?个人认为不要盲目创造,不过从多个方面考虑实现的方法,也是挺好的
[/Quote]
呵呵,你没明白我写了什么。我没有创造东西,而是把Qt底层的信号和槽的运行过程简化后呈现给大家。如果你看过Qt的源码,你肯定知道本文讲什么。如果你准备看源码,本文会对你有很大的帮助。如果你对Qt底层没有任何兴趣,本文对你用处也不大。
用心飞翔 2011-05-27
  • 打赏
  • 举报
回复
用qt提供的QSignalMapper不是能很好解决问题吗?个人认为不要盲目创造,不过从多个方面考虑实现的方法,也是挺好的
hitaka85 2011-05-25
  • 打赏
  • 举报
回复
mark
lgh2626 2011-05-24
  • 打赏
  • 举报
回复
不错不错,值得研究
duduqq 2011-05-03
  • 打赏
  • 举报
回复
不错不错,值得研究
dbzhang800 2011-05-03
  • 打赏
  • 举报
回复
[Quote=引用 4 楼 tiantang3gm 的回复:]

boost signal和signal2是不是也ok呢
[/Quote]
恩,不过本文的目的是换个角度介绍Qt底层的实现。其实就是简化的Qt的信号和槽的系统,所有的东西和Qt底层对应的。

我想应该可以,帮助大家理解Qt底层的实现,或者有助于阅读Qt的源码。
tiantang3gm 2011-05-03
  • 打赏
  • 举报
回复
boost signal和signal2是不是也ok呢

16,216

社区成员

发帖
与我相关
我的任务
社区描述
Qt 是一个跨平台应用程序框架。通过使用 Qt,您可以一次性开发应用程序和用户界面,然后将其部署到多个桌面和嵌入式操作系统,而无需重复编写源代码。
社区管理员
  • Qt
  • 亭台六七座
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧