cyt
发现原来已经有两个多月没有写blog了。近来工作都很忙,回到家都懒懒的不想动。虽然工作上做了很多有趣的东西,但毕竟有版权保护,在公司做的工作不能直接写在blog上,也要抽些时间整理一下才能放上来。
    今天在 www.codeproject.com 上又发现比较好玩的东西:http://www.codeproject.com/cpp/acfdelegate.asp
或者去 http://acfproj.sourceforge.net/.

    作者甚是好耐心开发了一套和C#用法差不多的C++库。当然这里只是着重讨论他的Delegate实现。其实Delegate也不是什么新技术,正如下文有人提出的boost.singal (http://www.boost.org/doc/html/signals.html)、另外还有:

Delegate - Extended Callback Library    http://callbackext.berlios.de
Libsigc++                   http://libsigc.sourceforge.net
libSigCX(对libSigC++的扩展) http://libsigx.sourceforge.net
sigslot                        http://sigslot.sourceforge.net/
slotsig                        http://slotsig.sourceforge.net/
              这个和上面的sigslot不是同一个东西,作者起的名字……一点新意都没有,而且作者还提供了一个性能比较的benchmark,比较了qt、boost、libsigc++和自己,结果可以看这里:http://slotsig.sourceforge.net/doc/benchmarks.html
作者还说:The sigslot library has not been benchmarked, because it provides too less features than the others。当然Qt用的是完全另外一套东西,通过预编译实现的signal / slot,个人觉得完全没有可比性,当然Qt的实现方法也是我个人所讨厌的,非要多一个预编译器出来,开发时候极为不爽。

    找回以前的代码,我在2002年的时候原来也已经实现过一个signal / slot,不过在slotsig作者眼中也是provides too less features。不过当然是自己够用就行了。之所以要“重复开发”,原因之一是以上的很多lib不支持VC6,当时我还在用破破烂烂的VC6;一方面我也不愿意自己的project里面塞一大堆lib,特别是boost的,庞大的吓人;一方面也就是自己能力的锻炼;还有一方面就是发现有些lib里面的实现用了些trick(例如用byte array保存指针),这也是个人不喜欢的东西。

    其实实现的原理很简单,先以一个简单的例子说明:
假设需要处理   int    f(int a);的singl / slot,如果单是函数指针,那就太简单了,slot就是函数指针,singal就是函数指针的数组。但现在还要加上对象的成员函数,于是就会想到把这两种东西合并起来,但从外面看有相同的接口。其实function object就是这样一种东西,不过function object只能在编译时期实现普通函数和成员函数接口统一,但现在需要在运行时期,很自然就会想到了虚函数:
struct slot1base
   { virtual int fun(int a) = 0; };

typedef  SmartPotint   slot1;

struct singal1
{
    int emit(int a) {    /* call each slot in m_listSlot */ }
    void connect( slot1 s) { m_listSlot.push_back(s); }

protected:
    std::list   m_listSlot;
};
其中SmartPoint就是带引用计数的智能指针,很多实现的库,随便选一个吧。
很明显,我们现在就是需要一个singal1,然后往它里面的m_listSlot里面不断放slot1base的子类。现在要做的事情,就是要把不同的形如 int f(int)样式的函数调用(包括普通函数和成员函数)统统转换为slot1base的子类。于是就有很多子类出来了:

struct slot1func : public slot1base
{
 typedef int (* func)(int);
 
 slot1func(func p) : m_func(p) {}
 virtual int fun(int a) { return m_func(a); }
private:
 func m_func;
};

template
struct slot1objfunc : public slot1base
{
 typedef int (obj::*func)(int);
 
 slot1objfunc(obj * p, func pF) : m_obj(p),m_pfunc(pF) {}
 virtual int fun(int a) { return (m_obj->*m_func)(a); }
private:
 obj * m_obj;
 func m_pfunc;
};

其实除了这两种以外,还可以扩展出更多的子类,例如能自动进行函数参数的类型转换的子类:
struct slot1func_for_short : public slot1base
{
 typedef short (* func)(short);
 
 slot1func_for_short(func p) : m_func(p) {}
 virtual int fun(int a) { return m_func(a); }
private:
 func m_func;
};
当然可以抽象为template
template
struct slot1func_for_other : public slot1base
{
 typedef R2 (* func)(A2);
 
 slot1func_for_other(func p) : m_func(p) {}
 virtual int fun(int a) { return m_func(a); }
private:
 func m_func;
};
同样还应该有for object function的版本……
除了这些,还可以有更多的,例如处理object的const函数的;处理本来没有返回值,每次需要虚拟一个返回值的函数;参数个数不一致,需要另外bind参数的……

现在有了很多子类以后,我们当然不希望要记着这么多子类的名称,希望使用的时候只通过同一个接口就可以生成我们所需要的slot1,这时候就是经典的template function出场了:
slot1 slot(int (*func)(int))
 { return slot1( new slot1func(func) ); }
template
 slot2 slot(obj * p, int (obj::*func)(int) )
  { return slot1( new slot1objfunc(p,func) ); }
template
 slot2 slot( R2 (*func)(A2) )
  { return slot1( new slot1func_for_other(func) ); }

应用的时候就可以:
int    func1(int a) { ..... }
struct MyObj {    int func2(int a) { .... } };

MyObj     obj;

signal1      onButtonClick;
onButtonClick.connect( slot( func1) );
onButtonClick.connect( slot( *obj, func2) );
return onButtonClick.emit( 100 );

原理很简单吧,但这个只是int func(int)格式的slot1,如果参数不是int,返回值不是int呢?简单,把上面的东西变成模版就行了。
template
struct slot1base
   { virtual R fun(A1 a) = 0; };

template
struct slot1 : public  SmartPotint >   {}

template
struct singal1
{
    R emit(A a) {    /* call each slot in m_listSlot */ }
    void connect( slot1 s) { m_listSlot.push_back(s); }

protected:
    std::list >   m_listSlot;
};

……后面的子类也作相应的处理。

原理是简单,不过要做一个完整的机制还要考虑更多的东西:
1、singal 和slot之间一般是“多对多”的关系,所以应该slot里面也有singal的列表,以方便双向查找。
2、多线程下的重入问题,如何加上锁,是需要仔细考虑的。
3、当一个singal里面包含多个slot的时候,那个返回值的处理,这个真的是多种多样了:
     只要其中一个的返回值;所有返回值记录在数组里面;当其中一个返回值是特定值时候不继续后面的slot……
     所以一般都会在singal1的模版参数中增加一个TMarshal的类来处理返回值:
     template
     struct singal1
     {
         R emit(A1 a)
         {
            typeMarsh marsh;
            /* for each item in m_listSlot*/
            {
                 if (! marsh.toContinue( (*it)->fun(a)))
                      break;
            }
            return marsh.value();
         }
    };
 而一个简单TMarshal可以是这样:
 template
  class TMarshal
  {
  public:
   TMarshal(void) : m_saveValue() {}
   static typeReturn defaultValue(void) { return typeReturn(); }
   typeReturn value(void) { return m_saveValue; }
   bool toContinue(const typeReturn & val)
    { m_saveValue = val; return true; }
  protected:
   typeReturn m_saveValue;
  };
4、现在只是讨论了一个参数的情况,多个参数的呢?好办,copy/paste一个参数的,加上多个参数就是了,例如:
 template
  struct slot2base
   {  virtual R fun(A1 a1, A2 a2) = 0; };
 不过的确是很烦人的工作,于是很多lib都是通过宏来实现,例如:
 一个 signalslot.imp的文件里面:
 template
  struct SLOTBASENAME
   { virtual R fun(FUNC_ARGS) = 0; };
 ……
 而singalslot.h里面就定义:
 #define TEMPLATE_ARGS typename A1
 #define FUNC_ARGS  A1 a1
 #define SLOTBASENAME slot1base
 #include "signalslot.imp"
 
 #define TEMPLATE_ARGS typename A1,typename A2
 #define FUNC_ARGS  A1 a1, A2 a2
 #define SLOTBASENAME slot2base
 #include "signalslot.imp"
 ……
 
 更有甚者,直接用perl来生成.h文件……
5、需要更多的接口,方便使用,例如slot的查找、删除、比较之类的。

posted on 2005-10-08 14:50 cyt 阅读(5390) 评论(2)  编辑 收藏 引用
Comments
  • # re: 我自己的signal / slot实现
    joyfire
    Posted @ 2005-11-26 00:24
    引用了这篇文章,可是找补到trackback:(  回复  更多评论   
  • # re: 我自己的signal / slot实现
    你好!
    Posted @ 2007-06-15 09:02
    你好, 看了你的文章感觉你好厉害得说!
    我不是专门研究程序的人,可现在我有点小困难,你肯定知道怎么解决
    你的文章里说:
    而singalslot.h里面就定义:
    #define TEMPLATE_ARGS typename A1
    #define FUNC_ARGS A1 a1
    #define SLOTBASENAME slot1base
    #include "signalslot.imp"

    #define TEMPLATE_ARGS typename A1,typename A2
    #define FUNC_ARGS A1 a1, A2 a2
    #define SLOTBASENAME slot2base
    #include "signalslot.imp"
    ……
    我运行程序时候就缺少这个文件,一直在寻找这个头文件,你能帮忙写给我吗?
    我的QQ346183499,全天再线
    谢谢了!  回复  更多评论   

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理