在编码过程中,我们经常会碰到一种场景:当某个业务触发的时候,需要通知到不同的模块,让各个模块对这些触发的业务进行不同的处理。这也就是设计模式中常见的生产者与观察者模式。在QT中,对于观察者模式有现成的封装好的信号与槽,可以很方便地使用,但是当进行代码移植或者在非QT环境上开发时,开发环境是没有提供现有的信号机制的,我们常常需要自己来实现,而在这种情况下,sigslot库是一个比较好的选择。
sigslot库, 它是用C++实现的具有类型安全,线程安全的信号和槽机制的库。它完全使用C++语言编写,只有一个头文件,因此避免了在使用的时候进行源码的预编译。sigslot的主页是http://sigslot.sourceforge.net。
Signal和Slots也是可以带一个或多个指定类型的参数。库sigslot是建立在C++的templated机制上的,所以在signal和solts的声明和调用的时候都会进行完全的类型检查。
signal类型的命名规则是signaln
信号的连接是使用方法类signaln<>的成员方法connect().方法connect()需要指定两个参数:
(1) 指向目标实例的指针
(2) 指向目标实例的成员方法的指针
为了让方法connect()能够工作,需要目标实例的类继承于has_slots.
一个信号是可以连接到任意数量的槽上的,当信号被发送的时候,所有连接到这个信号上的槽都会被调用。槽的调用对信号是无感的,所以槽方法的返回值都是void类型。
在现在的实现中槽方法是使用std::list进行存储的:
这也就意味着当一个信号连接了多个槽的时候,槽方法的调用顺序和其被连接的顺序是一致的。(在未来的版本中可能会对这个行为进行修改,因此我们不能确保槽方法的调用顺序一定会和其被连接的顺序一致。改进方法很简单,可以加优先级,将存储信号的容器换为优先队列,根据自己的业务需求进行拓展)
当信号实体或者槽实体被析构的时候,信号与槽的连接也就被断开了。但是在需要的时候我们还是可以调用signal的成员方法disconnec(&)来断开连接。
signal的disconnect()会断开当前信号与当前槽的连接单个连接,signal的disconnect_all()会断开当前信号与所有槽的连接,has_slotsdisconnec_all()可以用来断开与该槽连接的所有信号。
当信号发射时,通常称为信号触发,假设一个信号定义为:
signal2<char *, int> ReportError;
我们使用如下两种方法来发送这个信号:
ReportError("Something went wrong", ERR_SOMETHING_WRONG);
ReportError.emit("Something went wrong", ERR_SOMETHING_WRONG);
方法1是signal基类重载了函数操作运算符(),内部其实还是调用了emit函数。
sigslot库目前支持三种可选的信号线程策略:
(1)Single Threaded
在单线程模式下,库不会尝试跨线程保护其内部数据结构。因此,对构造函数、析构函数和信号的所有调用必须存在于单个线程中。
(2)Multithreaded Global
在多线程全局模式下,库使用单个全局关键部分保护其内部数据结构。这种方法在锁使用方面几乎没有开销或内存,但是锁的粒度比较大,有时可能会阻塞,因为所有信号共用一把锁。
(3)Multithreaded Local
在多线程本地模式下,库为每个对象使用单独的锁。这意味着每个信号都有自己的锁,当信号较多时,导致锁的创建开销加大,但是这种策略锁的粒度小,各个锁之间互不干扰。
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
#include
//信号的拥有者,可以做一个信号管理器,将所有信号都放在这个单例类中
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下载