深入理解信号槽.docx
- 文档编号:2173486
- 上传时间:2022-10-27
- 格式:DOCX
- 页数:18
- 大小:27.07KB
深入理解信号槽.docx
《深入理解信号槽.docx》由会员分享,可在线阅读,更多相关《深入理解信号槽.docx(18页珍藏版)》请在冰豆网上搜索。
深入理解信号槽
深入理解信号槽
(一)
这篇文章来自于ADeeperLookatSignalsandSlots,ScottCollins2005.12.19。
需要说明的是,我们这里所说的“信号槽”不仅仅是指Qt库里面的信号槽,而是站在一个全局的高度,从系统的角度来理解信号槽。
所以在这篇文章中,Qt信号槽仅仅作为一种实现来介绍,我们还将介绍另外一种信号槽的实现——boost:
:
signal。
因此,当你在文章中看到一些信号的名字时,或许仅仅是为了描述方便而杜撰的,实际并没有这个信号。
什么是信号槽?
这个问题我们可以从两个角度来回答,一个简短一些,另外一个则长些。
让我们先用最简洁的语言来回答这个问题——什么是信号槽?
∙信号槽是观察者模式的一种实现,或者说是一种升华;
∙一个信号就是一个能够被观察的事件,或者至少是事件已经发生的一种通知;
∙一个槽就是一个观察者,通常就是在被观察的对象发生改变的时候——也可以说是信号发出的时候——被调用的函数;
∙你可以将信号和槽连接起来,形成一种观察者-被观察者的关系;
∙当事件或者状态发生改变的时候,信号就会被发出;同时,信号发出者有义务调用所有注册的对这个事件(信号)感兴趣的函数(槽)。
信号和槽是多对多的关系。
一个信号可以连接多个槽,而一个槽也可以监听多个信号。
信号可以有附加信息。
例如,窗口关闭的时候可能发出windowClosing信号,而这个信号就可以包含着窗口的句柄,用来表明究竟是哪个窗口发出这个信号;一个滑块在滑动时可能发出一个信号,而这个信号包含滑块的具体位置,或者新的值等等。
我们可以把信号槽理解成函数签名。
信号只能同具有相同签名的槽连接起来。
你可以把信号看成是底层事件的一个形象的名字。
比如这个windowClosing信号,我们就知道这是窗口关闭事件发生时会发出的。
信号槽实际是与语言无关的,有很多方法都可以实现信号槽,不同的实现机制会导致信号槽差别很大。
信号槽这一术语最初来自Trolltech公司的Qt库(现在已经被Nokia收购)。
1994年,Qt的第一个版本发布,为我们带来了信号槽的概念。
这一概念立刻引起计算机科学界的注意,提出了多种不同的实现。
如今,信号槽依然是Qt库的核心之一,其他许多库也提供了类似的实现,甚至出现了一些专门提供这一机制的工具库。
简单了解信号槽之后,我们再来从另外一个角度回答这个问题:
什么是信号槽?
它们从何而来?
前面我们已经了解了信号槽相关的概念。
下面我们将从更细致的角度来探讨,信号槽机制是怎样一步步发展的,以及怎样在你自己的代码中使用它们。
程序设计中很重要的一部分是组件交互:
系统的一部分需要告诉另一部分去完成一些操作。
让我们从一个简单的例子开始:
//C++
classButton
{
public:
voidclicked();//somethingthathappens:
Buttonsmaybeclicked
};
classPage
{
public:
voidreload();//...whichImightwanttodowhenaButtonisclicked
};
换句话说,Page类知道如何重新载入页面(reload),Button有一个动作是点击(click)。
假设我们有一个函数返回当前页面currentPage(),那么,当button被点击的时候,当前页面应该被重新载入。
//C++---makingtheconnectiondirectly
voidButton:
:
clicked()
{
currentPage()->reload();//Buttonsknowexactlywhattodowhenclicked
}
这看起来并不很好。
因为Button这个类名似乎暗示了这是一个可重用的类,但是这个类的点击操作却同Page紧紧地耦合在一起了。
这使得只要button一被点击,必定调用currentPage()的reload()函数。
这根本不能被重用,或许把它改名叫PageReloadButton更好一些。
实际上,不得不说,这确实是一种实现方式。
如果Button:
:
click()这个函数是virtual的,那么你完全可以写一个新类去继承这个Button:
//C++---connectingtodifferentactionsbyspecializing
classButton
{
public:
virtualvoidclicked()=0;//Buttonshavenoideawhattodowhenclicked
};
classPageReloadButton:
publicButton
{
public:
virtualvoidclicked(){
currentPage()->reload();//...specializeButtontoconnectittoaspecificaction
}
};
好了,现在Button可以被重用了。
但是这并不是一个很好的解决方案。
引入回调
让我们停下来,回想一下在只有C的时代,我们该如何解决这个问题。
如果只有C,就不存在virtual这种东西。
重用有很多种方式,但是由于没有了类的帮助,我们采用另外的解决方案:
函数指针。
/*C---connectingtodifferentactionsviafunctionpointers*/
voidreloadPage_action(void*)/*onepossibleactionwhenaButtonisclicked*/
{
reloadPage(currentPage());
}
voidloadPage_action(void*url)/*anotherpossibleactionwhenaButtonisclicked*/
{
loadPage(currentPage(),(char*)url);
}
structButton{
/*...nowIkeepa(changeable)pointertothefunctiontobecalled*/
void(*actionFunc_)();
void*actionFuncData_;
};
voidbuttonClicked(Button*button)
{
/*calltheattachedfunction,whateveritmightbe*/
if(button&&button->actionFunc_)
(*button->actionFunc_)(button->actionFuncData_);
}
这就是通常所说的“回调”。
buttonClicked()函数在编译期并不知道要调用哪一个函数。
被调用的函数是在运行期传进来的。
这样,我们的Button就可以被重用了,因为我们可以在运行时将不同的函数指针传递进来,从而获得不同的点击操作。
增加类型安全
对于C++或者Java程序员来说,总是不喜欢这么做。
因为这不是类型安全的(注意url有一步强制类型转换)。
我们为什么需要类型安全呢?
一个对象的类型其实暗示了你将如何使用这个对象。
有了明确的对象类型,你就可以让编译器帮助你检查你的代码是不是被正确的使用了,如同你画了一个边界,告诉编译器说,如果有人越界,就要报错。
然而,如果没有类型安全,你就丢失了这种优势,编译器也就不能帮助你完成这种维护。
这就如同你开车一样。
只要你的速度足够,你就可以让你的汽车飞起来,但是,一般来说,这种速度就会提醒你,这太不安全了。
同时还会有一些装置,比如雷达之类,也会时时帮你检查这种情况。
这就如同编译器帮我们做的那样,是我们出浴一种安全使用的范围内。
回过来再看看我们的代码。
使用C不是类型安全的,但是使用C++,我们可以把回调的函数指针和数据放在一个类里面,从而获得类型安全的优势。
例如:
//re-usableactions,C++style(callbackobjects)
classAbstractAction
{
public:
virtualvoidexecute()=0;//sub-classesre-implementthistoactuallydosomething
};
classButton
{
//...nowIkeepa(changeable)pointertotheactiontobeexecuted
AbstractAction*action_;
};
voidButton:
:
clicked()
{
//executetheattachedaction,whateveritmaybe
if(action_)
action_->execute();
}
classPageReloadAction:
publicAbstractAction
//onepossibleactionwhenaButtonisclicked
{
public:
virtualvoidexecute(){
currentPage()->reload();
}
};
classPageLoadAction:
publicAbstractAction
//anotherpossibleactionwhenaButtonisclicked
{
public:
//...
virtualvoidexecute(){
currentPage()->load(url_);
}
private:
std:
:
stringurl_;
};
好了!
我们的Button已经可以很方便的重用了,并且也是类型安全的,再也没有了强制类型转换。
这种实现已经可以解决系统中遇到的绝大部分问题了。
似乎现在的解决方案同前面的类似,都是继承了一个类。
只不过现在我们对动作进行了抽象,而之前是对Button进行的抽象。
这很像前面C的实现,我们将不同的动作和Button关联起来。
现在,我们一步步找到一种比较令人满意的方法
深入理解信号槽
(二)
多对多
下一个问题是,我们能够在点击一次重新载入按钮之后做多个操作吗?
也就是让信号和槽实现多对多的关系?
实际上,我们只需要利用一个普通的链表,就可以轻松实现这个功能了。
比如,如下的实现:
classMultiAction:
publicAbstractAction
//...anactionthatiscomposedofzeroormoreotheractions;
//executingitisreallyexecutingeachofthesub-actions
{
public:
//...
virtualvoidexecute();
private:
std:
:
vector
//...oranyreasonablecollectionmachinery
};
voidMulti
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 深入 理解 信号
![提示](https://static.bdocx.com/images/bang_tan.gif)