当前位置: 首页 > 工具软件 > sigslot > 使用案例 >

Gtalk源码剖析之:sigslot介绍

赫连俊悟
2023-12-01
 
Sigslot
介绍,基于sigslot作者的介绍文档,讲述sigslot由来原理。
简介:
sigslot是一个线程安全、类型安全,用C++实现的sig/slot机制(sig/slot机制就是对象之间发送和接收消息的机制)的开源代码库。作者: Sarah Thompson sigslot sourceforget 上的连接为 http://sigslot.sourceforge.net/ ,可以到那里获取关于 sigslot 的最新消息。
 
sigslot起源
现代的C++项目通常包含大量的C++类和对象,对象之间通过成员函数调用,缺点是当类和对象规模很大时,相互之间必须记住对方提供了哪些接口,以及接口的详细信息,不易维护。
比如:我们有一个switch类和一个light类,而我们现在需要将两者关联起来,即通过switch控制light的状态,我们可能需要添加一个另外的类ToggleSwitch来将两者关联起来:
class Switch
{
public :
     virtual void Clicked() = 0;
};
class Light
{
public :
     void ToggleState();
     void TurnOn();
     void TurnOff();
};
 
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);
这在功能上完全可以实现,但想象一下如果大量的需要相互交互消息的类,用这种方式维护起来会是什么场景?
 
使用sig/slot机制来解决上述情况,不需要关心关联类的接口细节,sigslot实现的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;
Sigslot机制实现该功能与第一种方法相比,switch类多了个signal成员,light类需要从has_slots<>继承,其他没有什么变化。但省去了编写继承类用来实现两者关联的ToggleSwitch,对于sigslot机制我们只需要
 
sw1.Clicked.connect(&lp1, &Light::ToggleState);
sw2.Clicked.connect(&lp2, &Light::ToggleState);
 
就可以让两者之间建立关系,并且不需要修改类的代码就可以使用将来的变化,比如,如果想添加两个额外的灯和开关;或者增加一个按钮控制所有的灯,只需要
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());
很简洁,很优雅,不是吗?是不是还没明白,还有些糊涂?没关系,让我们继续向下,很快就会柳暗花明!
 
 
参数类型
sig/slot,什么是sig,什么是slot?
让我们明确一下,上例中继承has_slots<>的类light的成员函数void ToggleState();我们称之为slot;而switch类的signal0<> Clicked;称之为sig;sigslot的核心就在这里,就是通过这两个建立对应关系来实现对象间的消息交互。
我们可以看出slot是一个成员函数,而sig是一个成员变量,这里所说的slot参数类型是指对应的成员函数的参数类型,需要注意的是slot的原形需要与sig一致。嗯??读者可能奇怪一个成员函数如何与一个变量类型一致?从何说起?呵呵,慢慢来,sigslot库的实现使用了signaln <type1, type2, ...>这种书写方式,其中n表示signal可以接收几个参数,上面的列子中switch类的signal0<> Clicked;我们知道Clicked这个sig参数个数为n=0,而light的成员函数void ToggleState();参数个数也是0,这是巧合吗?不是,sigslot库正是以这种机制实现的。读者可能想起,函数原型除了函数参数以外还有返回值类型阿,没错,sigslot库的作者认为没必要支持各种类型返回值的slot,限制了只支持返回值为void类型的slot,OK,具体细节我们到sigslot源代码中再看。
 
Sigslot库用法
发送信号
信号(sig,即sig/slot的sig,下面提到的信号等同于此含义):
signal1<char *, int> ReportError;
比如上面的一个ReportError这个信号,当调用ReportError("Something went wrong", ERR_SOMETHING_WRONG);时候,将自动调用ReportError的emit成员函数发出一个信号。发给谁呢?^_^
连接信息号
通过调用sig的connect函数建立sig和slot间的对应关系。Connect函数接收两个参数,一个是消息目的对象的地址(指针),另一个是目的对象的成员函数指针(slot)。为了让整个机制有效运行,目的类必须从has_slots<>继承,并且sig/slot参数类型必须一致。
也可以将一个sig连接到多个slot上,这样每次sig发出信号的时候,每个连接的slot都能收到该信号。
断开信号连接
通过调用sig的disconnect函数断开sig和slot之间的连接,只有一个参数:目的对象的地址。一般不需要显式调用disconnect函数,在sig类和目的类(包含slot函数的类)析构函数中将自动调用disconnect断开sig和slot的连接。
 
也可使用disconnect_all断开该sig的所有slot。
signal0<> Bang();
Bang.connect(&bomb, &Bomb::Explode);
Bang.connect(&bomb2, &Bomb::Explode);
Bang.connect(&secret_base, &SecretBase::SelfDestruct);
Bang.disconnect_all();
Bang(); // Safely defused!
 
slot
如上所述slot就是普通的成员函数,但有以下限制:
1、返回值必须为void
2、Slot参数个数范围为0-8个
3、实现slot的类必须继承自has_slots<>
1,2是sigslot库作者的限制,作者权衡各方面因素后做出的决定,如果你觉得有必要你可以修改sigslot代码取消该限制,而3是sigslot的机制基础,必须遵守,除非你自己重新写个sigslot
 
需要注意的是:sigslot库的设计,当发送一个没有连接的信号时,不做任何处理,也不会有错误发出。
 
 
 类似资料: