你的浏览器不支持canvas

简单实现Qt的信号槽

Date: Author: zhangn

本文章采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可。转载请注明来自zhangn的云笔记

#include <stdio.h>
#include <string.h>
#include <vector>
#include <map>
#include <iostream>

# define slots
# define signals
# define emit

class Object;

// 首先,我们定义一个元对象,用来存放对象的所有信号和槽的关键字
// active是供信号函数调用的静态函数,用来查找并调用对应的槽函数
struct MetaObject 
{
	std::vector<const char *>signal_name;
	std::vector<const char *>slot_name;
	static void active(Object * sender, int index);
};

// 然后,我们定义一个绑定结构,用来将槽的对象和其槽函数进行关联
struct Connection
{
	Object * receiver;
	int method;
};

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


// 接下来我们就可以声明object类了
class Object 
{
	friend class MetaObject;
public:
	// 首先,我们先声明一个元对象
	MetaObject meta;

	// 然后,就是关键的绑定函数
	// 在绑定函数中主要做两件事
	// 1,查找对象的信号和槽的索引
	// 2,将索引进行绑定
	// 因此,我们还需要一个查找索引的辅助函数和一个用来存放绑定关系的map
	static void connect(Object*, const char*, Object*, const char*);
	static int find_index(std::vector<const char *> &vec, const char * str);
	// 辅助函数,根据索引调用槽函数,在qt中,该函数由moc自动生成
	void metacall(int idx);  
private:
	ConnectionMap connections;


// 信号和槽的函数可以声明在object中,也可以声明在子类中,为了测试方便,就不用子类了
// 信号和槽函数没什么特别的,就是普通的成员函数
// 关键字signals和slots并不是必要的,这个仅仅是为了让别人知道,这是信号和槽函数
// 或者是给一些自动化工具看的,比如qt的moc
public signals :
	void signal1();
	void signal2();

public slots:
	void slot1();
	void slot2();

};

// 实现active函数
// 在信号对象中,找到相应编号的信号和其绑定的所有槽函数
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);
	}
}

// 查找信号和槽的索引编号
int Object::find_index(std::vector<const char *> &vec, const char * str)
{
	int i;
	for (i = 0; i < vec.size(); ++i)
	{
		if (!strcmp(str, vec[i]))
			return i;
	}
	return -1;
}

void Object::connect(Object* sender, const char* signal, Object* receiver, const char* slot)
{
	// 找到发送者的信号索引和接受者的槽索引
	int signal_index = Object::find_index(sender->meta.signal_name, signal);
	int slot_index = Object::find_index(receiver->meta.slot_name, slot);
	if (signal_index == -1 || slot_index == -1) {
		perror("signal or slot not found!");
	}
	else {
		// Connection用来存放接受者对象和其槽函数的索引
		// 同一个对象的不同槽函数写成多个Connection
		// 每个Connection对应一次信号调用
		Connection c = { receiver, slot_index };  
		// 将接受者的Connection绑定到发送者的信号索引上
		sender->connections.insert(std::pair<int, Connection>(signal_index, c));
	}
}

void Object::slot1()
{
	printf("this is slot1!\n");
}

void Object::slot2()
{
	printf("this is slot2!\n");
}

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

// 信号函数实现,active的第二个参数就是信号索引
// 信号函数实现的过程也是将信号函数索引绑定的过程
// 在qt中,这已过成有moc完成,并写在moc_xxx.cpp中
void Object::signal1()
{
	MetaObject::active(this, 0);
}

void Object::signal2()
{
	MetaObject::active(this, 1);
}

using namespace std;

int main()
{
	Object obj1, obj2;

	// 将信号索引与字符串绑定
	// 借助vector的特性简单实现
	// vector的索引就是信号函数的索引
	// 当然,qt中比这要复杂,并且此过程有moc在moc_xxx.cpp中实现
	obj1.meta.signal_name.push_back("signal1");
	obj1.meta.signal_name.push_back("signal2");
	obj2.meta.slot_name.push_back("slot1");
	obj2.meta.slot_name.push_back("slot2");

	// 绑定信号槽,发射信号,这个就和在qt中常用的方法一样了
	Object::connect(&obj1, "signal1", &obj2, "slot1");
	Object::connect(&obj1, "signal2", &obj2, "slot2");
	// emit也是给人看的,即使在qt中,没有emit直接调信号函数也是可以的
	emit obj1.signal1();
	emit obj1.signal2();
	return 0;
}

对于本文内容有问题或建议的小伙伴,欢迎在文章底部留言交流讨论。