一片枫叶's profile追梦的人BlogLists Tools Help

Blog


    March 22

    类似boost库function的免delete委托实现

    困难不因你害怕而不出现,只因你勇敢面对迎刃而解
                         ------ 鼓励已成大龄程序员的自己
                        
    近来试验着重写一个C++的委托功能,就是.net里的delegate关键字描述的东东,似乎C++里喜欢说是成员函数closure,记不太清楚了。
    原来的实现跟loki库里的思路差不多,就是利用虚函数描述委托函数的原型,这个原型与具体实现类无关,子类实现函数的功能,实现
    里可以记住具体实现函数的类实例,现在看来没有什么很巧妙的东东了:
    1、接口层,纯虚接口,与实现对象无关。
    class IEvent
    {
    public:
    virtual ~IEvent(){}
    public:
    virtual bool operator () (const EventArg& arg)=0;
    };
    2、实现层,利用模板参数化实现对象类型。
    template<typename Obj>
    class Event : public IEvent
    {
     typedef bool (Obj::*Fptr)(const EventArg&);
    public:
     Event(Obj* pObj,Fptr fptr)
     {
      mObj=pObj;
      mFunc=fptr;
     }
     bool operator () (const EventArg& arg)
     {
      return (mObj->*mFunc)(arg);
     }
    private:
     Obj* mObj;
     Fptr mFunc;
    };
    3、创建函数,利用模板推导自动提供对象类型。
    template<typename Obj>
    IEvent* create(Obj* pObj,bool (Obj::*fptr)(const EventArg&)
    {
     return new Event<Obj>(pObj,fptr);
    }
    boost库里的function不使用虚函数机制,而是使用安全的强制转换实现了这种委托功能,它的核心技术在于对成员函数closure
    的对象封装(对象指针可以与普通指针互相强制转型)以及模板类的静态成员函数指针(函数原型可以不依赖模板类的模板参数
    ,而函数实现可以使用模板类的模板参数,从而实现虚函数接口与实现动态绑定的类似效果)。
    委托的实质其实是用一个普通函数原型(实现类无关的)关联一个函数closure对象(绑定到具体实现类的),一个是接口层的,
    一个是实现层的。
    不管是虚函数的实现还是基于强制转型的实现,closure对象的数据部分主要包含一个对象指针与一个函数指针,这两个指针在委
    托的生命周期内是必须有效的,委托无需关心它们指向对象的释放,委托需要关心的是closure对象实例的释放,因为closure对象
    有实现类的信息,不能直接作为委托的数据成员(否则委托方就被绑定到一个具体实现类了,而委托的涵义是可以有任意类型的实
    现类),所以基于虚函数的实现与boost库的实现均使用了堆内存分配closure对象,从而强加给委托方一个释放的职责。
    closure对象因为只是两个指针,占用的空间很小,如果不倒致类型依赖的话,作为委托方的数据成员是完全合适的,由此我们可
    以看到一个底层的实现,假设委托方类为Delegation,closure对象类为Closure:
    class Delegation
    {
    public:
     ...
     接口层的东西
     ...
    private:
     char space[sizeof(Closure)];
    };
    Delegation d;
    Closure c;
    *(Closure*)&(d.space)=c;//直接浅拷贝
    经过这样的处理,Delegation的成员space的空间里就会保存一个Closure实例的数据,在实现代码里再将此数据强制转成Closure
    对象也就没问题了(boost的实现里的模板类静态成员函数invoke里正适合做此工作),这里还有一个问题,就是sizeof(Closure)
    ,其中出现了Closure,会倒致Delegation对Closure的依赖,不过我们只是想知道Closure实例的大小,这个依赖似乎是可以消除的
    ,前面说过,Closure里主要是保存一个对象指针与一个成员函数指针,我们只需要伪造一个类似的虚拟类,用这个虚拟类来获取
    实例大小即可(也就是Delegation依赖一个虚拟类获取大小,这个大小却适用于任何Closure对象),因为涉及成员函数指针,C++对
    有多重继承的类与无多重继承的类的成员函数指针大小是不一样的,我们这里需要取尺寸的最大值,也就是使用一个多重继承的虚
    拟类。
    上面的方法是利用对象实例浅拷贝来存储实例数据的,与此类似,还有一个办法是像内存池的实现一样重载new操作,采用带placement
    参数的new重载,明确的将空间指定,下面我们给出基于重载new的实现,对我而言,用重载new感觉上比对象实例浅拷贝更优雅一些。
    //提供Closure存储空间的虚拟类
    struct ObjectPlaceHolder
    {
     //用来模拟实际情况类的虚拟类,成员函数指针在有继承与无继承下大小不一样,
     //ObjectPlaceHolder需要提供足够的空间供MemFuncInvoker...实例存储,所以需
     //要按多继承情形模拟成员函数指针。
     class DummyBaseA
     {};
     class DummyBaseB
     {};
     class DummyClass : public DummyBaseA,
      public DummyBaseB
     {};
    public:
     ObjectPlaceHolder()
     {
      clear();
     }
     ObjectPlaceHolder(const ObjectPlaceHolder& other)
     {
      obj=other.obj;
      fptr=other.fptr;
     }
     inline bool isNull(void) const
     {
      return obj==NULL || fptr==NULL;
     }
     inline void clear(void)
     {
      obj=NULL;
      fptr=NULL;
     }
    private:
     DummyClass* obj;
     void (DummyClass::*fptr)(void);
    };
    //Closure对象实现,同时实现了静态成员函数接口invoke,这是实现虚函数相似功能的关键
    //重载的new方法使用指定的空间作为对象实例化的空间,因为我们实际上是用的委托方的空
    //间,并不需要释放,所以delete方法什么也不做
    template<typename Delegation,typename T,typename R>
    struct MemFuncInvoker0
    {
    private:
     friend typename Delegation;
     typedef MemFuncInvoker0<Delegation,T,R> ThisType;
     typedef R (T::*Fptr)(void);
     MemFuncInvoker0(T* obj,Fptr func)
     {
      mObj=obj;
      mFunc=func;
     }
     static inline R invoke(const ObjectPlaceHolder* address)
     {
      const ThisType* pThis=reinterpret_cast<const ThisType*>(address);
      T* pObj=pThis->mObj;
      Fptr pFunc=pThis->mFunc;
      return (pObj->*pFunc)();
     }
     static inline void* operator new (size_t size,const ObjectPlaceHolder* address,size_t space)
     {
      _ASSERT(size<=space);
      return (void*)address;
     }
     static inline void operator delete(void*,const ObjectPlaceHolder* address,size_t space)
     {}
    private:
     T* mObj;
     Fptr mFunc;
    };
    //委托的实现
    template<typename R>
    struct Delegation0
    {
    public:
     typedef Delegation0<R> ThisType;
     typedef R (*InvokerType)(const ObjectPlaceHolder*);
    public:
     Delegation0()
     {
      detach();
     }
     Delegation0(const Delegation0& other)
     {
      mObj=other.mObj;
      invoker=other.invoker;
     }
     template<typename T>
     Delegation0(T* obj,R (T::*fptr)(void))
     {
      attach(obj,fptr);
     }
     template<typename T>
     inline void attach(T* obj,R (T::*fptr)(void))
     {
      invoker=MemFuncInvoker0<ThisType,T,R>::invoke;
      new(&mObj,sizeof(ObjectPlaceHolder)) MemFuncInvoker0<ThisType,T,R>(obj,fptr);
     }
     inline void detach(void)
     {
      mObj.clear();
      invoker=NULL;
     }
     inline bool isNull(void) const
     {
      return mObj.isNull() || invoker==NULL;
     }
     inline R operator () (void)
     {
      return invoker(&mObj);
     }
    private:
     ObjectPlaceHolder mObj;//用来保存MemFuncInvoker实例的空间
     InvokerType invoker;
    };
    上面的实现是对有一个返回类型(含void)无参数的函数样式的委托,一个典型用法如下:
    class A
    {
    public:
     char* run(void)
     {
      if(!onRun.isNull())
       return onRun();
     }
    public:
     Delegation0<char*> onRun;
    };
    class B
    {
    public:
     char* test(void)
     {
      return "B::test called !";
     }
    };
    int main(void)
    {
     A a;
     B b;
     a.onRun.attach(&b,&B::test);
     std::cout<<a.run()<<std::endl;
     ::_getch();
     return 0;
    }
    可以看到动态的将A::run委托到B::test的实现了。