第110讲 signal — slot

原本打算说filesystem的具体应用的,但想想算了,既然大家都知道的他一些操作,那么真正的使用就只差历练而已,好吧,我们今天继续新的东西,信号和槽。

关于信号和槽的概念,如果有接触过Qt,那么再熟悉不过,当然如果对Qt不熟悉,对MFC又或者是Win32的SDK比较熟悉的话,那么可以把他想象为时间和回调,当然如果接触过C#的话,可以把他理解为时间和委托。

恩,说了这么多,好像这些东西都和特定的环境有关,比如Qt,比如MFC,比如Win32的SDK,又或者是C#,那么在通常的C++中我们是否可以使用这么机制呢?当然是可以的了。这就是我们要说的boost库的signal库。

signal库的样子大概像下面这样,他拥有7个模板参数,但是通常我们都是很关心这么多参数,因为除了第一个模板参数需要我们指定外,其余的都有自己的默然类型,那么为什么我们需要指定第一个模板类型呢?恩,因为信号也需要一个类型标签,再简单点说,这里的信号是由operator()触发的,但是operator()也有参数限制,那么问题来了,这里的参数多少是怎么确定的呢?答案就是在我们制定的第一个模板类型,恩,这里大家可能会有疑问,大家还记得我曾经说过的function吗?

pedef std::function<void(int, int)> RECALL_RESHAPE;

看到这个typedef还有印象吗?所以简单点说,这里就是需要指定一个函数类型,函数类型包括返回类型和参数类型


template<class Signatrue

…….>

class signal : public signal_bas{

public:

…….

//主要成员如下

connection connect(const slot_type&

,connect_position = at_back);

connection connect(const group_type&

group,const slot_type&

,connect_position = at_back);


void disconnect(const group_type&

);


void disconnect(const slot_type&

);

bool empty() const;

………

};


现在我们可以很简单的声明一个signal对象:

signal<void()>sig;


现在假设我们需要通过触发sig信号让另外的函数得到响应:


void test1(){

std::cout<<"

slot1 have been calledn"

;

}

那么我们只需要connect该函数就好:

sig.connect(test1);

sig();

// operator()触发信号


对于多参数的信号问题,只需要在声明的时候指定参数就好:

//===============================================

void test1(const std::string&

str){

std::cout <<"

slot1 have been calledn"

;

}


void test2(const std::string&

str){

std::cout <<"

slot2 have been called "

<<str <<std::endl;

}


int main(){

boost::signals2::signal<void(std::string)>sig;

sig.connect(test1);


sig.connect(test2);


for (int i = 0;

i <100;

++i){

std::cout <<i <<std::endl;

if (i == 50)

sig.disconnect(&

test2);

sig("

hello …. "

);

}


return 0;

}

//================================================

编译运行程序会发现总是test1函数先被调用,那么能改变这个顺序吗?答案是肯定的,在connect的时候我们可以给他指定位置的,当然不是随意的了,我们只能将他们放在后面或者前面,默认情况下在后面,所以我们才会发现调用顺序总是和我们connect的顺序一致,如果我们将后面connect进来的数据放在前面的话,我们只需要将connect_position指定为at_front即可。有兴趣的可以自行尝试一下。

对于connect,还有一个重载是什么呢?恩,那就是我们可以给connect进来的slot指定group,这似乎就和signal的第三个模板参数扯上关系了,好吧,我承认,如果要说这个重载的问题,那么我们还是来看看第三个模是想要我们告诉他什么呢,第三个模板参数就是slot编组的问题,默认是int,其实没有修改,当然如果我们想要使用string来左边group的话,那么我们就需要指定这个模板类型了,只是一旦我们需要修改第三个,那么第二个自然也需要明确指定类型了。

说到group,那么什么类型可以作为group的类型呢?恩,只要是有operator<操作符的类型都可以作为group参数,如果没有提供operator<,那么你去修改第四个模板吧,因为第四个模板参数是说明group的比较方式,默认是std::less<第三个>,既然说了第三个模板参数,那么第二个还是说一下吧,虽然真的不想理睬他,因为发觉使用的时候很少会用到这玩意。

关于第二个模板参数是一个函数对象,被称为合并器,什么?合并器,难不成是想要将所有slot的结果给合并起来了,恩,就是的了,但是这个有什么意义呢?那就等需要这个功能的时候再来讨论,反正我到现在也还没用到这玩意过,默认情况下是返回最后一个slot的返回信息,返回结果是一个optional,需要自己对结果进行判断:

if(optional){

T* t = *optional;

}


兜了一圈,关于常用的四个模板参数我们都了解了,也知道怎么connect一个slot和断开一个slot,那么现在有一个问题,那么就是connect返回的connection到底是个啥玩意?这个其实是个好东西,能够灵活的管理slot的connect,现在继续来看看这个connection到底是个啥玩意,他看起来如下:

class connection{

public:

…..


void disconnect() const;

bool connected() const;

bool blocked() const;


void swap(const connection&

);

};

从这里我们看出来,这个connection管理connect相当的灵活,还能够阻塞,同时还支持比较,还支持复制和赋值(signal是不支持复制和赋值操作),下面我们来演示一下connection的操作:


//=============================================


void test1(){

std::cout <<"

slot1 have been calledn"

;

}


void test2(const std::string&

str){

std::cout <<"

slot2 have been called "

<<str <<std::endl;

}


int main(){

boost::signals2::signal<void()>sig;

boost::signals2::connection c1 = sig.connect(test1);

boost::signals2::connection c2 = sig.connect(std::bind(test2,"

hello …. "

));



for (int i = 0;

i <100;

++i){

std::cout <<i <<std::endl;

if (i == 50){

if (c2.connected())

c2.disconnect();

}

sig();

}


return 0;

}


//=============================================

如果我们不使用connection来管理connect的话,那么我们想要断开slot2(也就是test2)就会遇到一些小问题,关于这个大家可以自行尝试。


scope_connection是connection的子类,他的功能如名字一般:

{

scope_connection sc = sig.connect(std::bind(test2,"

hello …. "

));

} // 程序走到此处,自动disconnect


大家也看到了,connection不存在connect功能,只有查询功能,所以当我们disconnect之后就不可能再connect了,除非重来一遍,但是有个方法可以避免这种无聊的操作,还记得connection有一个查询阻塞的函数吗?所以信号可以阻塞的,shared_connection_block就是为了完成这项功能的,我们可以看下操作:


//=============================================

….


for (int i = 0;

i <100;

++i){

std::cout <<i <<std::endl;

if (i >50 &

&

i <90){

boost::signals2::shared_connection_block block(c2);

//对C2进行阻塞

sig();

}

else{

sig();

}

}

……

//============================================


在一开始的signal的概览中,我们看到一个slot_type的东西,那么这是什么鬼呢?这是signal2里面定义个的一个类,但是不是slot_type,而是slot,然而slot我们不能直接使用,因为他和signal关联,所以我们通常是使用typedef出来的slot_type。到目前为止我们似乎已经看到了所有的功能都能够满足,干嘛还需要这个不能直接使用的玩意呢?答案是程序可能会出现未定义行为,那么什么情况下会有这种问题呢?当我们connect的slot被销毁时,我们并没有disconnect,而直接emit signal时,这时候就会出现会定义行为了,这个东西是挺恐怖的,什么事都能够做得出来,而slot就是解决这个问题的,当connect的对象被销毁时,他会帮你disconnect,说了这么多,现在我们来看看具体的用法:

//=============================================

boost::shared_ptr<std::function<void()>>__F(new std::function<void()>(std::bind(test2,"

Hello … "

)));

boost::signals2::connection c2 = sig.connect(*__F);

__F.reset();


这将会导致未定义情况,接下来我们使用slot来解析这个问题:

typedef boost::signals2::signal<void()>sig_type;

sig_type sig;

boost::signals2::connection c1 = sig.connect(test1);

boost::shared_ptr<std::function<void()>>__F(new std::function<void()>(std::bind(test2,"

Hello … "

)));

sig.connect(sig_type::slot_type(*__F).track(__F));

__F.reset();

// 销毁对象也不会有问题

sig();

//=========================================

第110讲  signal --- slot

第110讲  signal --- slot


ok,就到这里吧,下一讲我们使用他来给大家演示一下在设计模式中的使用方式。


//=============================

回复D或者d查看目录,回复数字查看相应章节


原文始发于微信公众号(

C/C++的编程教室

):第110讲 signal — slot

|

发表评论