在C++使用信号和槽(sigslot库介绍)

一,使用场景

     在编码过程中,我们经常会碰到一种场景:当某个业务触发的时候,需要通知到不同的模块,让各个模块对这些触发的业务进行不同的处理。这也就是设计模式中常见的生产者与观察者模式。在QT中,对于观察者模式有现成的封装好的信号与槽,可以很方便地使用,但是当进行代码移植或者在非QT环境上开发时,开发环境是没有提供现有的信号机制的,我们常常需要自己来实现,而在这种情况下,sigslot库是一个比较好的选择。

二,sigslot库简介

     sigslot库, 它是用C++实现的具有类型安全,线程安全的信号和槽机制的库。它完全使用C++语言编写,只有一个头文件,因此避免了在使用的时候进行源码的预编译。sigslot的主页是http://sigslot.sourceforge.net。

1,参数类型

     Signal和Slots也是可以带一个或多个指定类型的参数。库sigslot是建立在C++的templated机制上的,所以在signal和solts的声明和调用的时候都会进行完全的类型检查。
     signal类型的命名规则是signaln<type1, type2> n是参数的个数。signal最多支持8个参数。
在这里插入图片描述

2,信号连接与槽

     信号的连接是使用方法类signaln<>的成员方法connect().方法connect()需要指定两个参数:
在这里插入图片描述
(1) 指向目标实例的指针
(2) 指向目标实例的成员方法的指针

     为了让方法connect()能够工作,需要目标实例的类是继承于类has_slots.
     一个信号是可以连接到任意数量的槽上的,当信号被发送的时候,所有连接到这个信号上的槽都会被调用。槽的调用对信号是无感的,所以槽方法的返回值都是void类型。
     在现在的实现中槽方法是使用std::list进行存储的:
在这里插入图片描述
     这也就意味着当一个信号连接了多个槽的时候,槽方法的调用顺序和其被连接的顺序是一致的。(在未来的版本中可能会对这个行为进行修改,因此我们不能确保槽方法的调用顺序一定会和其被连接的顺序一致。改进方法很简单,可以加优先级,将存储信号的容器换为优先队列,根据自己的业务需求进行拓展)

3,信号与槽的断开

     当信号实体或者槽实体被析构的时候,信号与槽的连接也就被断开了。但是在需要的时候我们还是可以调用方法signal的成员方法disconnec(&)来断开连接。
在这里插入图片描述
     调用signal的disconnect()会断开当前信号与当前槽的连接单个连接,调用signal的disconnect_all()会断开当前信号与所有槽的连接,调用has_slotsdisconnec_all()可以用来断开与该槽连接的所有信号。

4,信号的触发

     当信号发射时,通常称为信号触发,假设一个信号定义为:

signal2<char *, int> ReportError;

     我们使用如下两种方法来发送这个信号:

ReportError("Something went wrong", ERR_SOMETHING_WRONG);
ReportError.emit("Something went wrong", ERR_SOMETHING_WRONG);

     方法1是signal基类重载了函数操作运算符(),内部其实还是调用了emit函数。

5,信号的线程安全

sigslot库目前支持三种可选的信号线程策略:
(1)Single Threaded
     在单线程模式下,库不会尝试跨线程保护其内部数据结构。因此,对构造函数、析构函数和信号的所有调用必须存在于单个线程中。
(2)Multithreaded Global
     在多线程全局模式下,库使用单个全局关键部分保护其内部数据结构。这种方法在锁使用方面几乎没有开销或内存,但是锁的粒度比较大,有时可能会阻塞,因为所有信号共用一把锁。
(3)Multithreaded Local
     在多线程本地模式下,库为每个对象使用单独的锁。这意味着每个信号都有自己的锁,当信号较多时,导致锁的创建开销加大,但是这种策略锁的粒度小,各个锁之间互不干扰。

6,槽的线程安全

     sigslot库不能自动保证槽是线程安全的,但是它也提供了两个可用的锁。has slots类继承多线程策略,依次提供成员函数lock()和unlock(),用于保护内部数据结构用于实现信号/插槽机制。比如:

class MyMultithreadedClass
: public has_slots<multi_threaded_local>
{
public:
	void Entry1() // Slot
	{
		lock();
		...
		unlock();
	}
void Entry2() // Slot
	{
		lock();
		...
		unlock();
	}
};

     当然为了避免开发人员使用时忘记了调用unlock(),sigslot也封装了智能锁用于替换手动锁:

class MyMultithreadedClass
: public has_slots<multi_threaded_local>
{
public:
	void Entry1() // Slot
	{
		lock_block<multi_threaded_local> lock(this);
		...
	}
	void Entry2()
	{
		lock_block<multi_threaded_local> lock(this);
		...
	}
}

三,样例演示

     以一个简单的场景来举例:当客户端与服务端的连接断开时(可是本地网络问题,也可能是服务端主动断开),客户端的各个模块需要监听到这个信号触发,用于处理不同的逻辑:比如主界面需要有断线弹框提示,比如数据显示界面要将数据清空。

#pragma once

#include "sigslot.h"
#include <memory>
#include <iostream>

//信号的拥有者,可以做一个信号管理器,将所有信号都放在这个单例类中
class NetAdapter
{
	typedef sigslot::signal1 < const int32_t &> NetStateSignal;
public:
	static NetAdapter* GetInstance()
	{
		return s_instance.get();
	}

	NetStateSignal& GetNetStateSignal()
	{
		return net_state_signal_;
	}

public:
	void SetNetState(const int32_t& nState)
	{
		net_state_signal_.emit(nState);
	}
	
private:
	static std::shared_ptr<NetAdapter> s_instance;

	NetStateSignal net_state_signal_;
};
std::shared_ptr<NetAdapter> NetAdapter::s_instance(new NetAdapter);


//槽1:主界面
class MainWnd : public sigslot::has_slots<>
{
public:
	MainWnd()
	{
		NetAdapter::GetInstance()->GetNetStateSignal().connect(this, &MainWnd::OnNetStateChanged);
	}
	~MainWnd()
	{
		NetAdapter::GetInstance()->GetNetStateSignal().disconnect(this);
	}
public:
	void OnNetStateChanged(const int32_t &nState)
	{
		//主界面处理网络状态变化的逻辑
		std::cout << "MainWnd Signal Callback, State = " << nState << std::endl;
	}
};


//槽2:数据界面
class DataWnd : public sigslot::has_slots<>
{
public:
	DataWnd()
	{
		NetAdapter::GetInstance()->GetNetStateSignal().connect(this, &DataWnd::OnNetStateChanged);
	}
	~DataWnd()
	{
		NetAdapter::GetInstance()->GetNetStateSignal().disconnect(this);
	}
public:
	void OnNetStateChanged(const int32_t &nState)
	{
		//数据界面处理网络状态变化的逻辑
		std::cout << "DataWnd Signal Callback, State = " << nState << std::endl;
	}
};

     main函数中简单的调用一下:

#include "Signal.h"

int main()
{
	MainWnd mainWnd;
	DataWnd dataWnd;

	NetAdapter::GetInstance()->SetNetState(0);

	NetAdapter::GetInstance()->SetNetState(1);

	return 0;
}

     windows编译输出如下:
在这里插入图片描述
     linux下gcc编译输出:
在这里插入图片描述
     需要注意的是:下载原版sigslot.h,在gcc上编译由于typename的使用会导致模板编译报错,只需要将所有用到typename的地方还原一下就好了,附上此例的源码下载(支持linux编译):demo下载

发布了78 篇原创文章 · 获赞 79 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/bajianxiaofendui/article/details/104579303
今日推荐