sigslot的使用和分析
基本使用
实例
先看实例:
class Light
{
public:
Light(bool state)
{
state_ = state;
Displaystate();
}
void ToggleState()
{
state_ = !state_;
Displaystate();
}
void TurnOn()
{
state_ = true;
Displaystate();
}
void TurnOff()
{
state_ = false;
Displaystate();
}
void Displaystate()
{
std::cout << "The state is " << (state_ ? 1:0) << std::endl;
}
private:
bool state_;
};
class ComSwitch
{
public:
virtual void Clicked() = 0;
};
class ToggleSwitch : public ComSwitch
{
public:
ToggleSwitch(Light& lp) : lp_(lp)
{
}
virtual void Clicked()
{
lp_.ToggleState();
}
private:
Light& lp_;
};
Light lp1(true), lp2(false);
ToggleSwitch csw1(lp1);
ToggleSwitch csw2(lp2);
csw1.Clicked();
csw1.Clicked();
csw2.Clicked();
csw2.Clicked();
例子非常简单, 对家庭灯,开关这一套系统的一个简单封装,ToggleSwitch类和Light类相互关联是通过类的成员函数相互调用实现;如果项目足够大,相互关联的类越来越多, 使用这种方式就不得不考虑一下几个问题?
- 各个类耦合大;
- 对象生命周期的问题;
- 多线程情况下是否安全等;
- 如果一个控制类要控制多个对象操作, 实现起来比较麻烦; 一个更好的解决方式是使用信号和槽的机制,通过这种方式可以实现模块之间的高内聚,低耦合;不用去关心太多类之间联系的细节;
class Light : public sigslot::has_slots<>
{
public:
Light(bool state)
{
state_ = state;
Displaystate();
}
void ToggleState()
{
state_ = !state_;
Displaystate();
}
void TurnOn()
{
state_ = true;
Displaystate();
}
void TurnOff()
{
state_ = false;
Displaystate();
}
void Displaystate()
{
std::cout << "The state is " << (state_ ? 1:0) << std::endl;
}
private:
bool state_;
};
class Switch
{
public:
sigslot::signal0<> Clicked;
};
Switch sw1, sw2, all_on, all_off;
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);
std::cout << "all on clicked off.." << std::endl;
all_on.Clicked.connect(&lp1, &Light::TurnOff);
all_off.Clicked.connect(&lp1, &Light::TurnOff);
all_off.Clicked.connect(&lp2, &Light::TurnOff);
std::cout << "sw1 sw2 clicked..." << std::endl;
sw1.Clicked();
sw2.Clicked();
std::cout << "all on clicked..." << std::endl;
all_on.Clicked();
std::cout << "all off clicked..." << std::endl;
all_off.Clicked();
sw1.Clicked.disconnect(&lp1);
sw2.Clicked.disconnect(&lp2);
all_on.Clicked.disconnect(&lp1);
all_on.Clicked.disconnect(&lp2);
all_off.Clicked.disconnect(&lp1);
all_off.Clicked.disconnect(&lp2);
与上例相比最大的改变是,Clicked
不再是一个纯虚函数,而是一个信号; 类Light
则需要继承has_slots
; Switch
类和Light
关联也变得非常清晰;该方式也不用关心关联对象生命周期的问题(后续分析源代码详细介绍)。
说明
- sigslot所有的定义都包含在sigslot命名空间下;
- 需包含头文件**#include
**;
参数类型
signal和slot均可以携带一到八个自定义类型参数;不同参数信号声明方式如下:
signaln<type1, type2...> Clicked;
其中n
表示参数个数;
实例:
class Window
{
public:
enum WindowState { Minimised, Normal, Maximised };
signal1<WindowState> StateChanged;
signal2<int, int> MovedTo;
signal2<int, int> Resized;
};
class MyControl : public Control, public has_slots<>
{
public:
void OnStateChanged(WindowState ws);
void OnMovedTo(int x, int y);
void OnResize(int x, int y);
};
Window w;
MyControl c;
w.StateChanged.connect(&c, &MyControl::OnStateChanged);
w.MovedTo.connect(&c, &MyControl::OnMovedTo);
w.Resized.connect(&c, &MyControl::OnResize);
唯一值得注意的就是信号和插槽在连接时参数要保持一致。
多线程
介绍
sigslot实现仅仅用到ISO C++
和C++标准库(STD), 所以可以在多平台使用; 至少在单线程模式下无需做任何修改, 多线程每个平台不太一样;
多线程支持, 在win32下需要头文件windows.h,或者需要pthread.h头文件在支持Posix Threads的系统下;
sigslot三种线程策略如下:
- Single Threaded 单线程模式,无需加锁;
- Multithreaded Global 多线程模式,全局锁;使用全局锁保护sigslot中间产生的数据;这种模式开销小,但有时会出现不必要的阻塞;
- Multithreaded Local 多线程,局部锁;也以为着每一个signal有自己的锁;只有在非常必须的情况下,才会加锁;代价就是会创建和维护大量的锁;
多线程设置
/*
1. signal
*/
// Single-threaded
signal1<int, single_threaded> Sig1;
// Multithreaded Global
signal1<int, multi_threaded_global> Sig2;
// Multithreaded Local
signal1<int, multi_threaded_local> Sig3
/*
2. has_slots
*/
//single_threaded 也可以是multi_threaded_global或者multi_threaded_local
class Control : public has_slots<single_threaded>
{
};
class ControlMt : public has_slots<multi_threaded_local>
{
public:
void Entry1() // Slot
{
lock();
...
unlock();
}
void Entry2() // Slot
{
lock();
...
unlock();
}
};
class ControlMt : 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);
...
}
};
虽然sigslot不是一个完整的线程库,但也提供完成线程安全的函数如上所示;
实现
类图
类说明
has_slots
该类是所有处理对象的基类,处理对象中每一个处理函数就相当于一个slot(槽), has_slots提供了信号的管理,管理该对象槽相关的所有信号; 对外该类提供一下几个接口:
- signal_connect 添加信号指针;
- signal_disconnect 删除指定信号;
- disconnect_all 删除与该对象相关的所有信号;删除信号之前会依次通知每一个信号切断与该类的关联;
还需要注意,has_slots还提供了拷贝构造函数, 拷贝构造不仅仅拷贝了信号集合, 而且也会拷贝与之信号集合中每个信号有关的其他处理对象(也就是”_connection_base0”集合);
_signal_base(n)
_signal_base 代表一个信号, 其中包含信号处理对象的链表, 完成信号处理添加, 调用等各种操作; 其后面的n代表函数可以有n个参数(1 - 8); 分别对应signal1-signal8; _signal_base处理对象链表; 每一个结点其实就是一个_connection_base0的指针; _connection_base0外部不感知;生命周期均有signal控制.
接口说明:
- _signal_base(n)拷贝构造函数: 信号的拷贝不是简单地对被拷贝者的list链表的拷贝, 它的机制是遍历list链表克隆其中有效的处理者,将其加入到自己的 list 链表中, 并将信号添加到处理者对象的集合中。
- _signal_base(n)析构函数: 清空list链表,并释放链表节点中指针的内存(_connection_base0指针),若处理者有效,通知其处理者对象(has_slot), 删掉该信号。
- is_empty: 清除无效处理对象; 判断信号处理对象是否为空;
- persist_disconnect_all: 清空列表, 释放内存;通知处理者,删除该信号;
- disconnect_all: 和 persist_disconnect_all相比, 没有做_connection_base0指针指向内存释放以及list的清除;
- disconnect: 标记指定处理对象为无效, 并在处理对象的信号集合中删除该信号;
- slot_disconnect: 基类接口, 解除指定处理者关联; 实际效果是信号的对应处理者被置位无效;
- slot_duplicate: 基类接口, 当oldtarget被拷贝时调用, 使得在oldtarget中的信号,增加处理者,新增加的处理者指向newtarget.
_connection_base(n)
_connection_base是一个信号的处理者链表的一个节点对象,纯虚类,内部只有一个有效状态标志, 从上面的分析中已经知道,在信号调用 disconnect, 或者 has_slots 被析构时,这个状态位变为 无效,此后信号即使被触发,也不会调用处理对象。
总结
sigslot功能和boost库中的信号Signals作用差不多;
sigslot优点
- 不用担心空回调,当回调对象析构时会自动disconnect;
- 支持多线程,线程安全,有锁;
sigslot缺点
- 只能回调void类型函数,不支持返回值; boost中的signals库架构类似,支持返回值;
- slot没有优先级,不能动态调整回调队列中的先后顺序;
sigslot限制
slot函数(被回调的函数)就是普通的成员函数,但有以下限制:
- 返回值必须为void;
- Slot参数个数范围为0-8个(可扩展);
- 实现slot的类必须继承自has_slots<>;
cuipf
