C++中的信号和槽


sigslot.h 源码及英文文档可以从这里下载https://github.com/EGeeks/sigslot

1 介绍

本文介绍了sigslot库,它使用C++实现类型安全,线程安全的信号/插槽机制。 该库完全使用C ++实现, 并且不需要对源代码进行预处理即可使用。 sigslot库主页http://sigslot.sourceforge.net/ , 先看看那里本文的最新版本,以及库本身的最新下载。

1.1 sigslot库范例

大多数传统的C++代码最终归结为一个(可能很多)类,它们通过调用彼此的成员函数进行互操作。 允许类以这种方式进行交互操作通常需要类相当详细地了解对方。 例如,一个家庭自动化系统可能包含如下几个类

class Switch
{
public:virtual void Clicked() = 0;
};class Light
{
public:void ToggleState();void TurnOn();void TurnOff();
};
如果我们想将开关“接线”至灯光,以便点击开关来切换灯光的状态,假设我们无法直接修改Switch或Light,我们需要执行以下操作:
class ToggleSwitch : public Switch
{
public:
ToggleSwitch(Light& lp)
{m_lp = lp;
}
virtual void Clicked()
{m_lp.ToggleState();
}private:Light& m_lp;
};Light lp1, lp2;
ToggleSwitch tsw1(lp1), tsw2(lp2);

这是足够公平的,但很难。更好的解决方案是使用信号和槽。 信号和槽允许类
不需要过于详细地如何连接在一起。 以下是Switch和Light的一个本地实现:

class Switch
{
public:signal0<> Clicked;
};class Light : public has_slots<>
{
public:void ToggleState();void TurnOn();void TurnOff();
};Switch sw1, sw2;
Light lp1, lp2;

主要变化是纯虚函数'Clicked()'已经消失,被一个信号取代。
Light类在很大程度上没有改变,只是它继承了'has_slots'。
而不是需要实现像ToggleSwitch这样混乱的派生类,现在可以'开关' 连接指示灯:

sw1.Clicked.connect(&lp1, &Light::ToggleState);
sw2.Clicked.connect(&lp2, &Light::ToggleState);

使用信号与槽后现在变得很清晰, 现在添加两个lights, 各自有自己的触发开关, 加一个全局的全开全关开关

Switch sw3, sw4, all_on, all_off;
Light lp3, lp4;sw3.Clicked.connect(&lp3, &Light::ToggleState);
sw4.Clicked.connect(&lp4, &Light::ToggleState);
all_on.Clicked.connect(&lp1, &Light::TurnOn());
all_on.Clicked.connect(&lp2, &Light::TurnOn());
all_on.Clicked.connect(&lp3, &Light::TurnOn());
all_on.Clicked.connect(&lp4, &Light::TurnOn());
all_off.Clicked.connect(&lp1, &Light::TurnOff());
all_off.Clicked.connect(&lp2, &Light::TurnOff());
all_off.Clicked.connect(&lp3, &Light::TurnOff());
all_off.Clicked.connect(&lp4, &Light::TurnOff());

1.2 参数类型

信号和槽可以选择使用任意类型的一个或多个参数。 该库C++模板实现,这意味着信号和槽声明是完全类型检查的。命名约定如下:signal n < type1, type2, ...> ;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);

值得记住的是,只有信号和槽的类型一致才能进行连接被执行 - 使用的名字并不重要。

2 库的使用

2.1 发射信号

当信号被触发时,这通常被称为发射信号。 信号声明:

signal1<char *, int> ReportError;

可以通过调用其函数操作符来使其发出信号。 在实践中,这看起来调用一个函数

ReportError("Something went wrong", ERR_SOMETHING_WRONG);
或者,您可以通过调用emit()成员函数来获得完全相同的结果:
ReportError.emit("Something went wrong", ERR_SOMETHING_WRONG);

2.2 连接信号

     通过调用信号的connect()成员函数来连接信号。 connect()有两个参数:一个指向目标类的指针和一个指向目标类成员函数的指针。为了实现这一点,所有类型都必须同意,并且目的类也需要继承'has_slots'。
     信号可以连接到任意数量的槽。 当一个信号emit被调用时,所有连接的槽被调用。 这就是为什么插槽始终具有void返回类型, 当信号emit调用,实现返回值是没有意义的。
     当前实现使用STL列表来实现槽连接列表。 意即该槽按与连接顺序相同的顺序调用。 但是可能依靠这个不明智,因为未来调整到sigslot库可能会改变这种行为。


2.3 断开信号

断开信号操作是非常罕见的, 因为离开作用域后自动断开信号,但是如果您需要这样做,您可以调用信号的disconnect()成员函数与目标类的指针:

signal1<int> Bang;
...
Bang.connect(&someobj, &SomeObj::OnBang);
...
Bang(123); // Calls someobj.OnBang()
...
Bang.disconnect(&someobj);
...
Bang(321); // No longer calls someobj.OnBang()


2.4 实现槽

插槽只是普通的成员函数,具有以下附加条件:
1. 槽必须有返回void
2. 槽必须有0到8个参数(可以是任何类型)。
3. 实现槽的类必须继承has_slots<>。

槽可以通过信号/槽机制调用,也可以直接作为普通成员函数调用。


2.5 完全断开信号

要从当前连接的所有槽中完全断开信号,请调用信号disconnect all()成员函数:

signal0<> Bang();
Bang.connect(&bomb, &Bomb::Explode);
Bang.connect(&bomb2, &Bomb::Explode);
Bang.connect(&secret_base, &SecretBase::SelfDestruct);
Bang.disconnect_all();
Bang(); // Safely defused!

2.6 完全断开槽的对象

为了便于完全断开实现一个或多个插槽的对象,有槽基类提供了disconnect_all()成员函数的功能。 调用disconnect all()会自动断开所有连接的信号:

class MyClass : public has_slots<>
{
public:void OnSpeedChange(double mph);void OnBrakesApplied(bool brakestate);
};MyClass car;
signal1<double> Speed;
signal2<bool> Brakes;
Speed.connect(&car, &MyClass::OnSpeedChange);
Brakes.connect(&car, &MyClass::OnBrakesApplied);
Speed(50.0); // This one gets through
Brakes(true); // So does this
car.disconnect_all();
Speed(31.5); // This one doesn’t get through

2.7 发送未连接的信号

发出未连接的信号不是错误, 这是一个有意的设计选择。 相反,如果没有连接到一个信号,如果它被发射,信号会被安静地忽略。 没有警告产生,因为这是正确的行为。
      这个决定的基本原理可能并不明显,但在实践中这使得某些种类的应用程序更容易编写。

考虑一个为字符串实现可视化编辑控件的可重用类:

class StringEdit : public has_slots<>
{
public:signal0<> OnReturnPressed;signal0<> OnTabPressed;signal1<char> OnKeyPressed;signal1<char *> OnTextChanged;void SetText(char* text); // Slotvoid ClearText(); // Slot...
};

这个类的一些可能的用途可能会找到所有可用信号。 然而,在很多情况下,一些信号将不会有用 - 因此,可以这样使用

if(OnReturnPressed.is_connected())
{OnReturnPressed();
}

纯粹为了避免警告,更简单的调用OnReturnPressed()应该是足够。

3 使用注意事项

sigslot库编写仅需要ISO C++和C++标准库(STD),因此很可能在大多数平台上工作不变,至少在单线程模式下是这样。 在线程安全的情况下使用sigslot目前支持Win32和支持Posix线程的系统(例如大多数Unix,最新的Linux变体,OpenBSD,FreeBSD,Windows 95,98,ME,NT3.51,NT4.0,Win2k,XP等)

3.1 库依赖

在ISO标准的模式下,当前版本的仅依赖于自身和标准模板库和list, 多线程支持需要Win32下的windows.h头文件或OS下的pthreads.h支持Posix线程。

3.2 多线程支持

sigslot库目前支持三种替代线程策略

Single Threaded(单线程策略) 在单线程模式下,库不会尝试跨线程保护其内部数据结构。因此,所有对构造函数,析构函数和信号的调用都是至关重要的必须存在于单个线程内。

Multithreaded Global(多线程策略) 全局在多线程全局模式下,该库使用一个单一的全局临界区来保护其内部数据结构。这种方法在使用方面的开销很小或内存,但由于只有一个关键部分被共享,所以有时可能会进行不必要的阻塞在所有对象之间。

Multithreaded Local(多线程本地) 在多线程本地模式下,库为每个对象使用一个单独的临界区。意味着每个信号都有其自己的关键部分,每个类都从其继承has_slots。这些关键部分仅在绝对必要时锁定,在大量多线程应用程序中使用大量信号/槽减少线程竞争。但是,这个有一定的代价,因为必须创建非常多的关键部分对象并保持。

有两种选择的方法来设置库的线程模式:全局或每个基础类。

所有的信号类和槽都带有一个额外的可选参数,它指定了用于该特定类的多线程策略:

// Single-threaded
signal1<int, single_threaded> Sig1;
// Multithreaded Global
signal1<int, multi_threaded_global> Sig2;
// Multithreaded Local
signal1<int, multi_threaded_local> Sig3

虽然应用程序可以自由地在内部使用任何线程模式组合,但这不是一个好主意结合需要彼此互操作的单线程和多线程策略。 这是一这是编译器不会出错的唯一“违规”,因此程序员要小心。然而,混合multi threaded global和multi threaded local 是允许的,因为两者都是正确的实现锁定语义


3.2.1 全局设置线程模式

定义预处理器变量SIGSLOT_PURE_ISO强制所有平台上的ISO C++遵从性。 这个关闭线程支持,所以线程模式自动设置为Single_Threaded。 如果说开关不存在,库试图找出正在使用的平台。 WIN32被定义,Win32被假定,并且线程支持被启用。 同样,如果__GNUG__被定义,则假定gcc和Posix线程。 如果您在Unix或类Unix操作系统上使用除gcc以外的其他内容,则可以定义SIGSLOT__USE__POSIX__RHREADS来强制使用Posix线程。

缺省线程模式由SIGSLOT_DEFAULT_MT_POLICY变量设置。 这个如果未定义,则默认为multi threaded global。 要全局设置线程模式,请确保在包含sigslot.h之前,SIGSLOT_DEFAULT_MT_POLICY已正确设置。

默认的线程模式用在线程模式没有明确指定的地方 - 如果是指定,这总是覆盖默认值。

3.2.2 槽的线程安全

sigslot库不会自动保证你的插槽是线程安全的。 你应该假设可以在'不方便的时间'调用插槽,并且应该相应地进行防守编程。

虽然sigslot不打算成为一个完整的线程库,但它确实包含了一些对于创建一个实现槽线程安全的类非常有用。 has_slots类继承多线程策略,它又提供成员函数lock()和unlock()。 这些函数分别锁定和解锁互斥锁,并用于保护内部数据结构用于实现信号/插槽机制。 你可以自己使用lock()和unlock()代码,

例如:

class MyMultithreadedClass
: public has_slots<multi_threaded_local>
{
public:
void Entry1() // Slot
{lock();...unlock();
}
void Entry2() // Slot
{lock();...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);...
}
};

当lock_block对象时,它会锁定传入对象所拥有的关键部分。 什么时候锁块对象超出范围,关键部分自动释放

3.3 命名空间

sigslot库将其所有定义放置在sigslot命名空间中。 为了清楚简洁起见,本文档中的例子都假命名空间已经打开,例如:

#include <sigslot.h>
using namespace sigslot;

与标准模板库的标准命名空间一样,这是个人选择/或本地编码标准是否明确使用'sigslot ::'或打开命名空间。

sigslot.h 中文文档相关推荐

  1. PyTorch官方中文文档:torch.optim 优化器参数

    内容预览: step(closure) 进行单次优化 (参数更新). 参数: closure (callable) –...~ 参数: params (iterable) – 待优化参数的iterab ...

  2. matlab中文文档_Linux下Matlab安装

    如果觉得文章好看,欢迎点赞.同时欢迎关注微信公众号:氷泠之路. 引言 抱歉国庆由于各种原因一直没空更新文章,啊啊啊啊啊.... 因为在忙各种各样的事情,都怪女朋友,另外也更新了"装备&quo ...

  3. Lodash 中文文档 (v3.10.1) - “Lang” 方法

    Lodash 中文文档 (v3.10.1) - "Lang" 方法 Translated by PeckZeg Original Docs: Lodash v3.10.1 Docs ...

  4. source insight3.5显示中文_Doxygen 中文文档

    Doxygen 中文文档 原文:http://doxygen.nl/manual 本文档摘取重点进行了介绍. Getting started doxygen是解析源文件和生成文档的主要程序.详细使用方 ...

  5. 使用JavaScript生成二维码教程-附qrcodejs中文文档

    使用javascript生成二维码 依赖jquery 需要使用到的库 https://github.com/davidshimj... DIV <div id="qrcode" ...

  6. PHP-redis中文文档 1

    PHP-redis中文文档 phpredis是php的一个扩展,效率是相当高有链表排序功能,对创建内存级的模块业务关系 很有用;以下是redis官方提供的命令使用技巧: 下载地址如下: https:/ ...

  7. PlantCV中文文档

    PlantCV中文文档 1. 简介 1. 欢迎来到PlantCV文档 总览 开始 教程 贡献 版本 2. PlantCV Namespace 2.1 PlantCV 2.1.1 分析颜色 2.1.2 ...

  8. 【Scikit-Learn 中文文档】数据集加载工具 - 用户指南 | ApacheCN

    中文文档: http://sklearn.apachecn.org/cn/stable/datasets/index.html 英文文档: http://sklearn.apachecn.org/en ...

  9. Scikit-Learn 中文文档】数据集加载工具 - 用户指南 | ApacheCN

    中文文档: http://sklearn.apachecn.org/cn/stable/datasets/index.html 英文文档: http://sklearn.apachecn.org/en ...

最新文章

  1. java excel读取操作,Java读取Excel并操作
  2. OpenCASCADE:使用扩展数据交换 XDE之几何尺寸和公差 (GDT)
  3. LightOJ 1370 - Bi-shoe and Phi-shoe
  4. [转] 深入浅出 妙用Javascript中apply、call、bind
  5. STemwin替换为MDK下的emwin
  6. ajax_post运用
  7. Atitit 短信 技术的概论 短信备份 attilax总结 1.1. 短信的历史 1 1.2. 短信长度 160字的长度限制灵感来自对明信片的研究。明信片。大多数明信片上的字符不超过160个
  8. Java链表创建及遍历方法
  9. 会议会展产业要善用信息技术提高活动运营管理效率
  10. TCSVT论文结构整理
  11. 联发科MT6750/MT6750T芯片处理器哪个性能比较好?区别在哪?
  12. 非线性方程(组):高维方程解法
  13. Js 根据经纬度坐标计算方位角
  14. java的数据类型:8大基本数据类型
  15. java如何解析mime编码_javamail中MimeUtility解码繁体字的问题
  16. html语言编辑方法,Html双击使文字可编辑的方法
  17. 夏至日环食奇趣天象将在中国天空上演 错过再等十年
  18. 加倍提升开发效率,继续深挖一下Lombok的使用
  19. java集合框架的接口_Java集合框架之Collection接口详解
  20. 西瓜书学习笔记——第一、二章

热门文章

  1. 用c语言编辑单片机,C51单片机C语言函数编辑
  2. oracle中的execute immediate
  3. webmagic mysql_webmagic使用
  4. YLKJ-HS300多合一读卡终端技术规格说明书
  5. linux img文件怎么打开,img文件扩展名,img文件怎么打开?
  6. 【优化算法】多目标蚁狮优化算法(MOALO)【含Matlab源码 1598期】
  7. 2023年天津仁爱学院专升本专业考试准考证下载打印通知
  8. 毕业一年:两次择业经历
  9. python无法卸载“No Python 3.10 installation was detected”,提示“Python 0x80070643安装时发生严重错误”
  10. 正则表达式取反/正则不包含