实现一个高效C++多分派委托类(new) [感谢 问题男 指点]

更新记录:
2005-07-23 0:51:  根据 问题男 的指点作了修改,废弃了汇编代码,提高了移植性。新版本下载

一、前言



委托的重要性就不用再介绍了吧? C++ 标准没有实现委托, VC 中实现的委托需要 CLR 支持,所以没有真正意义上的 C++ 委托。

今天仔细看了 i_like_cpp 翻译的《 成员函数指针与高性能的C++委托 》,觉得实现过于复杂;又看了 周星星 的《 类成员函数转化为普通函数(未完待续)(VC++6.0 & ASM 》,其中全以汇编来实现,没有实现出一个完整的 delegate ,似乎只是想验证 thiscall 的模拟。

前段时间我曾经实现过一个,不过在委托对象的成员函数时,要求类必须从某个基类派生 ( MFC 做的 ) ,虽然添加了析构时自动解除委托的功能,但前面这个要求却过于苛刻。

今天仔细想了一下,确实直接使用汇编来模拟 thiscall 调用是最快的,其实什么类型都可以抛弃,只要保证 [1 ecx 中存放对象地址; 2 、把参数压栈; 3 、调用 ( 汇编的 call) 函数所在的地址 ] 3 条即可,那么也就是说,我们可以把对象地址转为 void* ,成员函数指针转为 void* ,就可以用通用的方式存储了。调用时,如果对象地址为 0 则作为 stdcall 来调用,如果不为 0 ,则模拟 thiscall 调用。

二、主要技术难点及说明

<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

 

注:下面许多部分使用省略号,省略相似的内容。

1
、由于是多分派委托,所以使用了一个vector <pair<void*, void*> >来存放对象-函数指针对或者普通函数,需要说明的是,我经过测试发现vector使用下标访问,效率远远高于使用迭代器,所以本文中vector的遍历都是使用下标方式。

2
、由于参数个数有变化,经过考虑确定使用模板偏特化来实现,首先来看一下它的调用方式(模拟C#调用方式)

 

Delegate  < void >  f0;     //  定义一个void delegate ()委托
Delegate  < void int >  f1;   //  定义一个void delegate (int)委托
Delegate  < void int const   string &>  f2;     // 定义一个void delegate (int, const string&)委托

 

要实现这样的调用方式,模板声明如下:

 

class  NullType{  //  一个无法实例化的类,用作默认参数
private :
    NullType ();
};

template 
< typename Ret, typename A = NullType, typename B = NullTypedot.gifdot.gif >
class  Delegate
{
private :
    Delegate ();  
//  不可实例化
};

template 
< typename Ret >
class  Delegate  < Ret >
{
public :
    Ret 
operator  ( ) ( )
    {
        dot.gif.
    }
};

template 
< typename Ret, typename A >
class  Delegate  < Ret >
{
public :
    Ret 
operator  ( ) (A a )
    {
        dot.gif.
    }
};

 

3、其它要实现的还有:增加绑定(+=),清除绑定(=0),清除现有绑定并增加一个绑定(=func),本文为简化实现过程,暂时只实现了add函数即+=功能。add函数的有2个重载实现:


typedef Ret( * func)(dot.gif);
void  add (func f);   //  实现普通函数的绑定

template 
< typename T >
void  (T *  obj, Ret (T:: * )(dot.gif) f);  //  实现成员函数绑定。[注:由于语法的原因,这里使用了特殊方式,见第4小节]

 

4、由于C++语法的原因,不能直接使用Ret (T::*)(...)作为类型,所以这里增加了一个模板类Mem_Fun,用于提取类型信息。Mem_Fun类声明如下:

template  < typename Ret, typename Ty, typename A = NullType, typename B = NullType, typename C = NullType, 
    typename D
= NullType, typename E = NullType, typename F = NullType, typename G = NullType, 
    typename H
= NullType, typename I = NullType, typename J = NullType, typename K = NullType, 
    typename L
= NullType, typename M = NullType, typename N = NullType, typename O = NullType,
    typename P
= NullType, typename Q = NullType, typename R = NullType, typename S = NullType,
    typename T
= NullType, typename U = NullType, typename V = NullType, typename W = NullType,
    typename X
= NullType, typename Y = NullType, typename Z = NullType
>
struct  Mem_Fun
{
    typedef Ret(Ty::
* mem_fun0)();
    typedef Ret(Ty::
* mem_fun1)(A);
    typedef Ret(Ty::
* mem_fun2)(A,B);
    typedef Ret(Ty::
* mem_fun3)(A,B,C);
dot.gif

    typedef Ret (
* function0)();
    typedef Ret (
* function1)(A);
    typedef Ret (
* function2)(A,B);
    typedef Ret (
* function3)(A,B,C);
dot.gif
};


限于篇幅原因,这里省去若干行。(不过我真的定义了27个类型,是使用python脚本帮我输出的。)

使用这个模板类,上面3小节的add函数声明为:

void  add (Mem_Fun < Ret, NullType, dot.gif > ::functionN f);   //  实现普通函数的绑定

template 
< typename T >
void  add (T *  obj, Mem_Fun < Ret, T, dot.gif > mem_funN f);  //  实现成员函数绑定。

 

由于add函数功能比较通用,所以单独写了一个Delegate_Base类,在Delegate::add中只是简单调用Delegate_Base::add

 

5、前面已经讲过把成员函数指针转为void*存储,但C++编译器都不允许直接进行转型,尝试过使用union也无法编译,最后的解决办法是使用struct,如下:

struct  Pointer{
    Mem_Fun
< Ret, T, dot.gif > ::mem_funN p;
};
Pointer ptr 
=  {f}; 
void *  p  =   * ( void ** ) & ptr;


呵呵,转过来了。

 

6thiscall的模拟,这个在i_like_cpp和周星星的blog中都可以看到,其它相关的资料也比较多,其实就是增加一个汇编指令:mov ecx ptr,然后调用call mem_fun即可,当然要记得把参数压栈。这里摘录2段代码:

 

代码1(无参数委托偏特化的()函数)

typedef typename Mem_Fun < void , NullType > ::function0 func;

    
void   operator  ( ) ( )
    { 
        
for  (size_t i = 0 ; i < _handlers.size (); i ++
        { 
            
void *  p  =  _handlers[i].first; 
            
if  (p)  //  member function 
            { 
                
void *  mem_fun  =  _handlers[i].second; 
                __asm{ 
                    mov ecx, p 
                    call mem_fun 
                } 
            } 
            
else  
            { 
                (
* (func)_handlers[i].second)(); 
            } 
        } 
    }

 

代码2(有两个参数委托偏特化的()函数)

 

typedef typename Mem_Fun < void , NullType, A, B > ::function2 func;

    
void   operator  ( ) (A a, B b) 
    { 
        
for  (size_t i = 0 ; i < _handlers.size (); i ++
        { 
            
void *  p  =  _handlers[i].first; 
            
if  (p)  //  member function 
            { 
                
void *  mem_fun  =  _handlers[i].second; 
                __asm{ 
                    push b 
                    push a 
                    mov ecx, p 
                    call mem_fun 
                } 
            } 
            
else  
            { 
                (
* (func)_handlers[i].second)(a, b); 
            } 
        } 
    }


 

上面要注意的是参数压栈顺序。

 

7、由于多分派委托的返回值处理起来比较麻烦(i_like_cpp的译文里已经有说明),所以这里暂时没有做返回值。当然这不是什么麻烦事,有返回值和无返回值也可以通过偏特化来实现,没有去实现它的原因是,还没有确定在多分派情况下如何去处理。

8、[新加入]根据 问题男 的指点,修改成模拟thiscall调用,这里简单分析一下。

假定有void test3 (a, b, c)和void Test::test3 (a, b, c)这2个函数[注:这里不去考虑参数类型,Test::test3为nonstatic member function],void* p和void* f分别保存对象和函数指针,arg1, arg2, arg3是调用时的3个参数。先看一下调用过程:

调用test3:

void* p = (void*)0;
void* f = (void*)test3;
// 调用
__asm{ 
    push arg3
    push arg2 
    push arg1
    call f 
    add esp, 
12
}


调用Test::test3:

Test test;
typedef 
void  (Test:: * test_mem_fun)();
struct  Ptr{test_mem_fun f;};
Ptr ptr 
=  { & Test::test0};

void *  p  =  ( void * ) & test;
void *  f  =   * ( void ** ) & ptr;
//  调用
__asm{ 
    push c
    push b 
    push a 
    mov ecx, p 
    call f 
}


这个版本移植性不好,如果能让编译器帮我们产生调用代码,那是再好不过了。下面是让编译器帮我们生成调用,移植性也比较好。cdecl比较容易模拟,这里的thiscall是根据 问题男 的指点写成的。

调用test3:

void *  p  =  ( void * ) 0 ;
void *  f  =  ( void * )test3;
//  调用。假设int, string, float是3个参数的类型
typedef  void  ( * test3_func)( int string float );
(
* (test3_func)f)(a, b, c);


调用Test::test3:

Test test;
typedef 
void  (Test:: * test_mem_fun)();
struct  Ptr{test_mem_fun f;};
Ptr ptr 
=  { & Test::test0};

void *  p  =  ( void * ) & test;
void *  f  =   * ( void ** ) & ptr;
//  调用
typedef  void  (NullType:: * mem_fun3)( int string float );
mem_fun3 mf;
* ( void ** ) & mf  =  f;
(((NullType
* )p) ->* f)(a, b, c);


注:开始我还有点怀疑这里是不是可能存在虚函数调用的问题,后经测试证实没有这问题,我想是因为取成员函数指针时,编译器已经做了处理,而实际调用时根本不需要处理。(纯属个人观点,有大虾帮我证实一下?)

三、实现过程。

基本上就是个编写、测试、比较、拷贝修改这样一个无聊的过程了,呵呵,不说了。实际上我也没有实现完整,目前只实现了
3个参数的支持,修改代码是个麻烦事,我想等确定下来了,再把其它的偏特化实现补全。

 

四、代码下载。

 

Demo.rar (包括Deleagete.hDemo.cpp)

五、效率。

初步测试速度比较满意,最初的版本有些慢,是因为我使用迭代器的缘故,改为下标方式就快了十倍以上。就我测试的结果来看,使用这个委托类并没有比直接调用慢,可能只是不明显吧,不过测试效率也有些麻烦,有时甚至使用委托类比直接调用还快,但这是不可能的。所以我只能总结一点,那就是使用这个类并不比直接调用慢多少。。。。。。嗨嗨。。我想可能是因为在表格中定位所损失的效率,比起函数调用的开销来说显得微不足道吧,这和虚函数调用差不多,我记得有人说过虚函数调用平均会损失5%的效率,当然也和代码块长度有关。

转载于:https://www.cnblogs.com/cpunion/archive/2005/07/23/delegate_cpp_demo.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值