我是靠谱客的博主 动人便当,最近开发中收集的这篇文章主要介绍sigslot(c++信号槽库)源码分析,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

最近本来开始看libevent的源码的,里面看到一个叫Reactor模式,然后又感觉跟观察者模式有点像,就去找他们的区别,偶然又发现了一个信号槽的库sigslot。本来我是写过一段时间的QT的,体会过QT信号槽的便捷,竟然有人用c++写出这个库,当然是要看一下的。关于信号槽机制让一个信号与槽进行绑定,然后不管在任何位置发射信号,这个槽函数都会相应,所以方便性不言而喻。它的首页地址是http://sigslot.sourceforge.net/。
首页介绍写的有点意思~
The Microsoft Foundation Classes (MFC) are a nightmarish mess. I'll say it again - the MFC is a programmer's nightmare. At one point it almost inspired me to give up programming and take up something less irritating, like becoming a professional 'nut the javelin' player or move to Outer Patagonia and become a cat wrangler. Nevertheless, the MFC is capable of supporting most things that you might want to do with the Windows platform, at the price of a little slice of one's sanity.
大致意思说MFC就是一个程序员的噩梦,它曾经差点让作者放弃了编程,想去做养猫的,哈哈~ 

介绍
先整体介绍一下这个库,通过构造signal0,signal1...signal8类的对象可以用于绑定槽与发射信号,通过继承于has_slots<>类的对象,它的成员函数可以作为槽函数来进行绑定。一个信号可以绑定多个槽,同样一个槽也可以响应多个信号。通过拷贝构造可以复制它所有的信号槽,析构函数自动断开连接,所以一般不需要手动断开连接(除非必要)。里面添加了多线程支持,WIN32下与Linux下,Linux下我不是很懂,所以主要分析WIN32下,通过一个宏定义可以控制使用的是单线程还是多线程,多线程分两种,全局的与局部的,其实就是使用一个全局的临界区还是每个信号与槽分别使用一个临界区,一般全局的就可以,局部的效率应该会低很多。
从文档中给出的实例开始分析:

class Switch
{
public:
	signal0<> Clicked;//这里的信号是不带参数的,signaln表示带几个参数
};
class Light : public has_slots<>
{
public:
	Light(bool state) { b_state = state; cout << "init: "; Displaystate();  }
	void ToggleState() { b_state = !b_state; Displaystate(); } //作为消息的响应
	void TurnOn() { b_state = true; Displaystate(); }
	void TurnOff() { b_state = false; Displaystate(); }
	void Displaystate() { cout << "The state is " << b_state << endl; }
private:
	bool b_state;
};
int main() {
	Switch sw1, sw2, all_on, all_off;
	Light lp1(true), lp2(false);
	sw1.Clicked.connect(&lp1, &Light::ToggleState); //绑定
	sw2.Clicked.connect(&lp2, &Light::ToggleState);
	all_on.Clicked.connect(&lp1, &Light::TurnOn);
	all_on.Clicked.connect(&lp2, &Light::TurnOn);
	all_off.Clicked.connect(&lp1, &Light::TurnOff);
	all_off.Clicked.connect(&lp2, &Light::TurnOff);
	
	
	sw1.Clicked();//等价于sw1.Clicked.emit();
	sw2.Clicked();
	all_on.Clicked();
	all_off.Clicked();
}

运行结果如下:
init: The state is 1
init: The state is 0 
The state is 0        
The state is 1       
The state is 1        
The state is 1        
The state is 0        
The state is 0        

代码中Switch对象与Light对象进行绑定,开关发射按开关的信号,灯进行响应,可以很好的很方便的模拟现实中的这个场景
Switch类中的成员Clicked用于发射信号,它是signal0<>类的一个对象,可以看出,signa0是一个模板类,看一下它的源码:
 

template<class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
	class signal0 : public _signal_base0<mt_policy>
	{
	public:
		typedef _signal_base0<mt_policy> base;
		typedef typename base::connections_list connections_list;//std::list<_connection_base0<mt_policy> *>
		using base::m_connected_slots;//父类里的成员list,保存了连接好的槽函数
		signal0()
		{
			;
		}

		signal0(const signal0<mt_policy>& s)
			: _signal_base0<mt_policy>(s)
		{
			;
		}

		template<class desttype>
		void connect(desttype* pclass, void (desttype::*pmemfun)())
		{
			lock_block<mt_policy> lock(this);
			_connection0<desttype, mt_policy>* conn =
				new _connection0<desttype, mt_policy>(pclass, pmemfun);
			//此处是将一个信号绑定到多个槽上
			m_connected_slots.push_back(conn);
			//此处就要求待绑定的类必须要继承于has_slots,将this添加到sender中,但是注释了下面这行依然可以正常工作?
			pclass->signal_connect(this);
		}
		//发射信号时,将此信号连接的所有槽函数进行调用
		void emit()
		{
			lock_block<mt_policy> lock(this);
			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
			typename connections_list::const_iterator itEnd = m_connected_slots.end();

			while (it != itEnd)
			{
				itNext = it;
				++itNext;

				(*it)->emit();

				it = itNext;
			}
		}
		//重载 () 运算符
		void operator()()
		{
			lock_block<mt_policy> lock(this);
			typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
			typename connections_list::const_iterator itEnd = m_connected_slots.end();

			while (it != itEnd)
			{
				itNext = it;
				++itNext;
				//此处的emit其实就是(m_pobject->*m_pmemfun) 让绑定的成员调用它的成员函数
				(*it)->emit();

				it = itNext;
			}
		}
	};

模板参数是控制使用什么线程方式的,会发现源代码里很多lock_block<mt_policy> lock(this);这句代码,lock_block是一个类,里面有一个线程类的对象是mt_policy类型,
 

template<class mt_policy>
	class lock_block
	{
	public:
		mt_policy *m_mutex;

		lock_block(mt_policy *mtx)
			: m_mutex(mtx)
		{
			m_mutex->lock();
		}

		~lock_block()
		{
			m_mutex->unlock();
		}
	};

可以看到,构造函数进行锁住,析构释放锁,就跟lock_guard一样。mt_policy是等于宏SIGSLOT_DEFAULT_MT_POLICY,默认是#define SIGSLOT_DEFAULT_MT_POLICY single_threaded就是单线程类,单线程类则不保证在多线程环境下的线程安全问题。可以修改为在创建信号槽对象时模板参数给定multi_threaded_global或multi_threaded_local,这两个是线程类,比如看个global线程类的实现
 

	class multi_threaded_global
	{
	public:
		multi_threaded_global()
		{
			static bool isinitialised = false;

			if (!isinitialised)
			{
				InitializeCriticalSection(get_critsec());
				isinitialised = true;
			}
		}

		multi_threaded_global(const multi_threaded_global&)
		{
			;
		}

		virtual ~multi_threaded_global()
		{
			;
		}

		virtual void lock()
		{
			EnterCriticalSection(get_critsec());
		}

		virtual void unlock()
		{
			LeaveCriticalSection(get_critsec());
		}

	private:
		CRITICAL_SECTION* get_critsec()
		{
			static CRITICAL_SECTION g_critsec;
			return &g_critsec;
		}
	};

构造函数保证临界区对象只初始化一次,然后lock时进入临界区,unlock时出临界区。而信号槽类都继承于线程类,所以可以保证对象的信号槽机制是线程安全的。而Local_thread则是定义为成员变量,每次都获取,释放。整理一下线程这边,构造信号槽对象时给定模板参数single_threaded或multi_threaded_global或multi_threaded_local,然后lock_block类使用这个线程类对象进行控制临界区的访问,每个信号槽都继承于线程类,每项操作都要lock,所以保证了多线程下的线程安全,思路应该还是蛮清晰的。

回到signal0类中,有下面几个成员函数
 

signal0 (const signal0< mt_policy > &s) 
template<class desttype >
void 	connect (desttype *pclass, void(desttype::*pmemfun)()) 
void 	emit () 
void 	operator() ()

connect用于连接槽,emit与operator()从代码中可以看出作用相同,是用来发射信号用的。
再看一下它的成员变量:
 

typedef _signal_base0<mt_policy> base;
typedef typename base::connections_list connections_list;//std::list<_connection_base0<mt_policy> *>
using base::m_connected_slots;

就只有父类的的一个m_connected_slots对象,父类定义为:
typedef std::list<_connection_base0<mt_policy> *>  connections_list;;
connections_list m_connected_slots;
好了,其实就是一个list,里面保存的是_connection_base0<mt_policy>类型的指针。此时就要看一下signal0的父类_signal_base0的定义以及connection0的定义。删除了disconnect部分的代码,因为它有点长,且并“没有那么重要”。

template<class dest_type, class mt_policy>
	class _connection0 : public _connection_base0<mt_policy>
	{
	public:
		_connection0()
		{
			m_pobject = NULL;
			m_pmemfun = NULL;
		}

		_connection0(dest_type* pobject, void (dest_type::*pmemfun)())
		{
			m_pobject = pobject;
			m_pmemfun = pmemfun;
		}

		virtual ~_connection0()
		{
		}
		//clone是复制一个新的绑定好的槽与对象
		virtual _connection_base0<mt_policy>* clone()
		{
			return new _connection0<dest_type, mt_policy>(*this);
		}
		//duplicate将一个新的对象与本类的成员函数绑定到一起
		virtual _connection_base0<mt_policy>* duplicate(has_slots_interface* pnewdest)
		{
			return new _connection0<dest_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
		}

		virtual void emit()
		{
			(m_pobject->*m_pmemfun)();
		}
		//获取接收信号的槽函数的对象,即我发射信号,谁来响应
		virtual has_slots_interface* getdest() const
		{
			return m_pobject;
		}

	private:
		dest_type* m_pobject;
		void (dest_type::* m_pmemfun)();//槽函数的函数指针
	};
//=============================================
template<class mt_policy>
	class _signal_base0 : public _signal_base<mt_policy>
	{
	public:
		typedef std::list<_connection_base0<mt_policy> *>  connections_list;

		_signal_base0()
		{
			;
		}

		_signal_base0(const _signal_base0& s)
			: _signal_base<mt_policy>(s)
		{
			lock_block<mt_policy> lock(this);
			typename connections_list::const_iterator it = s.m_connected_slots.begin();
			typename connections_list::const_iterator itEnd = s.m_connected_slots.end();

			while (it != itEnd)
			{
				(*it)->getdest()->signal_connect(this);
				m_connected_slots.push_back((*it)->clone());

				++it;
			}
		}

		~_signal_base0()
		{
			disconnect_all();
		}

		bool is_empty()
		{
			lock_block<mt_policy> lock(this);
			typename connections_list::const_iterator it = m_connected_slots.begin();
			typename connections_list::const_iterator itEnd = m_connected_slots.end();
			return it == itEnd;
		}

		void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget)
		{
			lock_block<mt_policy> lock(this);
			typename connections_list::iterator it = m_connected_slots.begin();
			typename connections_list::iterator itEnd = m_connected_slots.end();

			while (it != itEnd)
			{
				//获取oldtarget对象绑定的槽,复制给newtarget
				if ((*it)->getdest() == oldtarget)
				{
					//此处的duplicate其实是将oldtarget对象绑定的槽跟newtarget绑定到一起
					m_connected_slots.push_back((*it)->duplicate(newtarget));
				}

				++it;
			}
		}

	protected:
		connections_list m_connected_slots;
	};

当信号对象调用connect时给定了对象与成员函数指针,然后内部构造了一个connection对象,然后添加到list中。再重复一下signal0中的connect函数的代码:
 

void connect(desttype* pclass, void (desttype::*pmemfun)())
{
		lock_block<mt_policy> lock(this);
		_connection0<desttype, mt_policy>* conn = new _connection0<desttype, mt_policy>(pclass, pmemfun);
		//此处是将一个信号绑定到多个槽上
		m_connected_slots.push_back(conn);
		//此处就要求待绑定的类必须要继承于has_slots,将this添加到sender中,用于拷贝构造时的复制
		pclass->signal_connect(this);
}

这部分应该不难理解,signal_connect(this);就是将当前信号添加到槽的senderSet集合中。所以信号的类结构如下

整理下信号类这边。signal类对象调用connect(),参数为一个继承于has_slot<>类对象与它的成员函数,connect用一个connection来保存下这组对应关系,添加到signal的成员list中,signal通过emit或operator()发射信号,即遍历这个connection_list,分别使用对象调用成员函数。通过拷贝构造可以将一个signal对象中保存的所有connection对象复制到新的signal对象中。析构函数自动释放所有连接

接着到Slot类这边,此类必须要继承于has_slots类,不然在signal对象调用connect时最后一行pclass->signal_connect(this);会告诉你没有signal_connect函数。看一下has_slots的类结构

看一下虚基类提供的接口
 

	class has_slots_interface
	{
	public:
		has_slots_interface()
		{
			;
		}

		virtual void signal_connect(_signal_base_interface* sender) = 0;

		virtual void signal_disconnect(_signal_base_interface* sender) = 0;

		virtual ~has_slots_interface()
		{
		}

		virtual void disconnect_all() = 0;
	};

就是一个连接,断开连接。has_slots类仅仅是实现了虚函数,没有增加新的接口。has_slots类中有一个成员,定义如下
typedef std::set<_signal_base_interface*> sender_set;
sender_set m_senders;

就是保存的是signal的基类指针,其实就是记录了自己所关心的信号,虽然不添加看起来也能正常工作,但是当把这个槽的对象复制给另一个对象时,它便不能接收到信号了。可以看一下拷贝构造部分的代码
 

has_slots(const has_slots& hs)
{
	lock_block<mt_policy> lock(this);
	const_iterator it = hs.m_senders.begin();
	const_iterator itEnd = hs.m_senders.end();
	
	while (it != itEnd)
	{
		(*it)->slot_duplicate(&hs, this);
		m_senders.insert(*it);
		++it;	
	}
}

里面对每个sender,即signal对象调用了slot_duplicate函数,看一下slot_duplicate的实现方式

void slot_duplicate(const has_slots_interface* oldtarget, has_slots_interface* newtarget)
{
    lock_block<mt_policy> lock(this);
	typename connections_list::iterator it = m_connected_slots.begin();
	typename connections_list::iterator itEnd = m_connected_slots.end();

	while (it != itEnd)
	{
		//获取oldtarget对象绑定的槽,复制给newtarget
		if ((*it)->getdest() == oldtarget)
		{
			//此处的duplicate其实是将oldtarget对象绑定的槽跟newtarget绑定到一起
			m_connected_slots.push_back((*it)->duplicate(newtarget));
		}

		++it;
	}
}

再看到每个connection对象调用了duplicate函数,那再看一下它的代码吧..
 

virtual _connection_base0<mt_policy>* duplicate(has_slots_interface* pnewdest)
{
	return new _connection0<dest_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
}

此处就是生成一个connection对象,与pnewdest绑定到一起。

整理一下:has_slots中的拷贝构造函数,对保存的每个sender(即signal对象)调用slot_duplicate,slot_duplicate遍历connection_list,对每个connection对象调用duplicate,将槽函数绑定到新的槽对象中,然后将生成的新的connection对象添加到connection_list中,这样的话每个sender发射信号时,这个新的槽对象也能响应这个信号,因为它们已经被添加到connection_list中。

到此,不带参数的部分就全部分析完了,我相信仔细看应该都能看明白。对于带参数的,一共最多可以带8个参数,此时在定义信号对象,槽对象时都需要带上这些个参数,且类型,个数必须相同。所以源码中就有了signal0,signal1...connction0,connection1...以及他们的基类。实现方式都差不多,无非就是加了个参数而已。

看完后觉得实现的还蛮巧妙,而且应该这种方法用其他语言应该都能实现。

 

最后

以上就是动人便当为你收集整理的sigslot(c++信号槽库)源码分析的全部内容,希望文章能够帮你解决sigslot(c++信号槽库)源码分析所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(55)

评论列表共有 0 条评论

立即
投稿
返回
顶部