龙盟编程博客 | 无障碍搜索 | 云盘搜索神器
快速搜索
主页 > 软件开发 > C/C++开发 >

细说C++委托和消息反馈模板(1)(2)

时间:2011-04-12 23:18来源:未知 作者:admin 点击:
分享到:
委托(Delegation) 委托是什么呢?委托最本质的是提供一种类型安全的动态消息响应转移机制。 以前,我对委托一无所知,我觉得无非就是一个类型安全的智能

委托(Delegation)

委托是什么呢?委托最本质的是提供一种类型安全的动态消息响应转移机制。

以前,我对委托一无所知,我觉得无非就是一个类型安全的智能指针,而所谓的Multi-Cast Delegation无非就是一个智能指针数祖,是不是还有Any-Cast Delegation呢?我不知道,也许有吧,无非就是智能指针数祖+随机数发生器。

但是,实际上并不是那么简单。你可以把我刚才说的函数指针封装一下弄一个类封装起来,不过,这直接导致某个消息的响应只能是固定死的函数指针类型,甚至不能是可爱的Functor或者是某个类的成员函数。你可能会跟我抬杠说这怎么可能,不是可以用template实现么?我们来看一个例子

假设某个委托类 Dummy_Delegation 拥有一个成员函数用来连接处理函数 template <class T> void Dummy_Delegation::Connect(T _F); 没错,_F可以不一定函数指针,也可以是Functor,我们利用_F()来呼叫响应函数,一切看起来是多么美好——但是,很不幸,这个_F无法保存下来供消息产生的时候呼叫。

一切都因为这个该死的template<class T> ,你无法在Dummy_Delegation内定义一个T类型的变量或者指针来保存_F。退一万步说,你把T作为整个Dummy的模版,还是避免不了在模版实例化的时候定死类型。于是,整个Delegation的通用性大打折扣。

实际上,我们希望有这么一种Delegation,他可以把消息响应动态绑定到任何一个类的成员函数上只要函数类型一致。注意,这里说的是任何一个类。这就要求我们屏蔽信号发生器和响应类之间的耦合关系,即,让他们相互都不知道对方是谁甚至不知道对方的类型信息。

这个方法可行么?Yes!

桥式委托(Bridge Delegation) ---- 利用泛型+多态来实现

请允许我杜撰一个名词:桥式委托(Bridge Delegation)

实现这么一个东西真的很有意思,其实,像gtk+/qt很多需要"信号/反馈"(signal/slot)的系统都是这么实现的。

说到GP和Template,那真的可以算是百家争鸣了,就像boost和loki还在争夺新的C++标准智能指针的地位打得不可开交。而Functor这个东西有是很多GP algo的基础,比如sort/for_each等等。

整个桥式委托的结构如下图:

  1. Signal <>-------->* Interface  
  2. ^  
  3. |  
  4. Implementation<Receiver> -------------> Receiver 

我们搭建了一个Interface/Implementation的桥用来连接Singal和Receiver,这样就可以有效隔开双方的直接耦合。用之前我们的Socket类来演示如下:

  1. struct Socket {  
  2. Signal OnRecv;  
  3. }; 

一个Receiver可以是一个function比如 void OnRecv1() 也可以是一个Functor:

  1. struct OnRecv2_t {  
  2. void operator() ();  
  3. } OnRecv2; 

我们可以这样使用这个桥式委托

  1. Socket x;  
  2. x.OnRecv.ConnectSlot(OnRecv1); //或者 x.OnRecv.ConnectSlot(OnRecv2());  

当消息产生调用 x.OnRecv()的时候,用户指定的OnRecv1或者OnRecv2就会响应。

我们来看看如何实现这个桥:首先是一个抽象类

  1. struct DelegationInterface {  
  2. virtual ~DelegationInterface() {};  
  3. virtual void Action() = 0;  
  4. };  

然后才是模版类Impl:

  1. templateclass T>  
  2. struct DelegationImpl : public DelegationInterface {  
  3. T _FO;  
  4. DelegationImpl(T _S) :_FO(_S) { }  
  5. virtual void Action() { _FO(); }  
  6. };  

注意我们上面的图示,这个DelegationImpl类是跟Receiver相关联的,也就是说这个Impl类知道所有的Receiver细节,于是他可以从容地调用Receiver()。再次留意这个继承关系,对了,一个virutal的Action函数!利用多态性质,我们可以根据Receiver来实例化DelegationImpl类,却可以利用提供一致的访问Action的Interface,这就是整座桥的秘密所在——利用多态下层隔离细节!

再看看我们的Signal类:

  1. struct Signal {  
  2. DelegationInterface* _PI;  
  3.  
  4. Signal() :_PI(NULL) {}  
  5. ~Signal() { delete _PI; }  
  6.  
  7. void operator()() { if(_PI) _PI->Action(); }  
  8. templateclass T> void ConnectSlot(T Slot) {  
  9. delete _PI; _PI = new DelegationImpl<T>(Slot);  
  10. }  
  11. }; 

显然,Signal类利用了 DelegationInterface* 指针_PI来呼叫响应函数。而完成这一切连接操作的正是这个奇妙的ConnectSlot的函数。对了!上次讨论模版函数的时候就说了这个T类型无法保存,但是这里用桥避开了这个问题。利用模版函数的T做为DelegationImpl的实例化参数,一切就这么简单地解决了。

你也许可能会抗议,认为我绕了一大圈又绕回了一开始我烦恼的继承/多态上面来了。其实,你有没有发现,我们这个Singal/Bridge Delegation/Receive的体系是固定的一套东西,你在实际使用中并不需要自己去继承去处理重载,你只需要好好地Connect到正确的Slot就可以了。这也可以算是一种局部隐含的继承吧。

接下来我们要讨论一下这个桥式委托的性能消耗以及扩展和局限性问题


精彩图集

赞助商链接