【C/C++】 高效C++委托的原理

原文地址:高效C++委托的原理


写这篇学习心得源自于一篇老外的文章Member Function Pointers and the Fastest Possible C++ Delegates,网上也有它对应的中文翻译,我随意找一篇:http://www.cnblogs.com/jans2002/archive/2006/10/13/528160.html

文章中提到了高效C++委托,不过讲得太生涩了,以至于第一次看,没怎么明白。这里我把一些理解记录下来,并且以易理解的方式介绍一下高效C++委托。

一.我理解的委托

所谓委托,就是把工作交给别人来完成。我的理解就是在程序中,把本类的函数指针交给其它类,让其它类在需要的时候调用这个函数,完成某个功能。说简单点,就是一个函数回调的机制。为了实现这样一个回调的功能,我们可以通过对外暴露接口的方式,让外部持有该类的接口,必要时进行接口的回调。但是这样增加了调用者(受托者)和被调用者(几乎是委托者)的耦合关系,因为调用者必须知道这个接口,需要include接口头文件。为了减少这种耦合,常用的技术就是使用boost::function对象。委托者把要委托的函数指针打包成一个function对象,扔给受托者,让受托者在合适的时机完成功能,这样两边都可以不知道对方是如何实现的,没有接口的耦合,大家都只要#include function对象就行了。


二 function的原理

boost::function或许都用过,觉得用起来还可以,但是一看它的源代码,真的头晕,我怀疑它的作者写这个就是不想让别人看懂的,所以这里建议初学看loki库的functor。其实function的代码核心没那么复杂,如果没有宏的嵌套,没有文件迭代,一下就能看明白。我这里写一个简单的模拟boost::function的例子:


[cpp]  view plain copy
  1. template<class T> class function;    
  2. template<typename R, typename A0, typename T>    
  3. class  function<R (T::*)(A0) >     
  4. {    
  5. public:    
  6.     typedef R(T::*fun)(A0);    
  7.      function (fun p, T* pthis):m_ptr(p), m_pThis(pthis){}    
  8.     
  9.     R operator()(A0 a)      {(m_pThis->*m_ptr)(a);}    
  10.     fun m_ptr;    
  11.     T* m_pThis;    
  12. };    
[cpp]  view plain copy
  1. class CA  
  2. {  
  3. public:  
  4.     void Fun(int a) {cout << a;}  
  5. };  
  6.   
  7. CA a;  
  8. function<void (CA::*)(int)>   f(&CA::Fun, &a);  
  9. f(4);  // 等价于a.Fun(4);  


其实function的作用就是封装了this指针和函数指针保存起来,到必要的时候再调用。但也不要以为真正的boost::function和boost::bind就是如此简单,这里只把它的核心机制进行了模拟(甚至都没有实现解耦)。


二 成员函数指针的效率

boost::function是对成员函数指针的一层封装,重载的()操作符对函数参数进行了一次转发,因此,function的效率和直接调用成员函数指针差不多,甚至还要慢。即使对每个参数加上引用转发,也不能改变这个现状。那么成员函数指针调用的效率有多少呢?看下面的实验

[cpp]  view plain copy
  1. class B1    
  2. {    
  3. publicvirtual void fb1()  
  4.         {  
  5.             printf("fb1");  
  6.         }  
  7. };    
  8. class B2    
  9. {  
  10. public:  
  11.     virtual void fb2(){}  
  12. };    
  13. class D1 : public B2, public B1    
  14. {  
  15. publicvirtual void fb1()  
  16.         {  
  17.             printf("d1fb1");  
  18.         }  
  19. };    
  20. class D2 : virtual public B1    
  21. {    
  22. publicvirtual void fb1()  
  23.         {  
  24.             printf("d2fb1");  
  25.         }  
  26. };   


画成类图就是

其中,D2是虚继承于B1,用粗箭头表示。

然后我们再写三个成员函数指针的调用,

第一个是调用自己的虚函数,第二个是调用多继承中的覆盖的第二个父类的虚函数,第三个是调用覆盖的虚基类的虚函数

[cpp]  view plain copy
  1. B1* pb1 = new B1;    
  2. void (B1::*fpb1)() = &B1::fb1;    
  3. (pb1->*(fpb1))();  
  4. assert(assert(sizeof(fpb1) == 4));  
  5.   
  6. D1* pd1 = new D1;    
  7. void (D1::*fpd1)() = &D1::fb1;   
  8. (pd1->*(fpd1))();    
  9. assert(sizeof(fpd1) == 8));  
  10.   
  11.   
  12. D2* pd2 = new D2;    
  13. void (D2::*fpd2)() = &D2::fb1;    
  14. (pb2->*(fpd2))();  
  15. assert(sizeof(fpd2) == 12));  


对于这三个调用,在VC编译器下看它的release反汇编代码如下。

第一个:

0040656B  mov       ecx,dword ptr [ebp-10h] 

0040656E  call        dword ptr [ebp-14h]

该处是取this指针,调用函数地址

第二个:

004065F0  mov         ecx,dword ptr [ebp-18h] 
004065F3  add         ecx,dword ptr [ebp-20h] 
004065F8  call        dword ptr [ebp-24h]

该处是取this指针,偏移this指针,再调用函数地址

第三个:

004066BD  mov         ecx,dword ptr [ebp-2Ch] 
004066C0  mov         edx,dword ptr [ecx] 
004066C2  mov         eax,dword ptr [ebp-34h] 
004066C5  mov         ecx,dword ptr [ebp-2Ch] 
004066C8  add         ecx,dword ptr [edx+eax] 
004066CB  add         ecx,dword ptr [ebp-38h] 
004066CE  mov         esi,esp 
004066D0  call        dword ptr [ebp-3Ch]

该处是取this指针,移到虚基类列表,取虚基类地址,取虚基类偏移,调用函数地址。

(有关原理请参考我的另一个学习心得文章:C++内存布局生成步骤

可见,成员函数指针在不同的情况下保存了不同的信息,做了不同的事。

第一种情况:
typedef void (B1::*FPB1)();   

{
mem function address;  // 4 bytes,成员函数地址
}

第二种情况:
typedef void (D1::*FPB1)();   


{
mem function address; // 4 bytes;,成员函数地址
delta  added to this pointer; // 4 bytes,this指针偏移
}

第三种情况:
typedef void (D2::*FPB1)();   

{

mem function address; // 4 bytes;,成员函数地址
delta  added to this pointer; // 4 bytes,this指针偏移
index in virtual base class table; // 4 byptes ,虚基类列表中的序号

}

大家也许做过成员函数指针转化为void*的事,无论怎么做,都编译不通过。只有解了成员函数指针的构成才会知道,void*才4个字节,无法容纳成员函数指针的信息。

在上面的三种情况中,第二种情况比第一种低效,第三种情况比第二种更低效。有没有什么方式让所有的成员函数指针调用都像第一种一样高效呢?答案是有的,即把所有的成员函数指针都当成第一类情况来处理。

 构造快速的委托

我们可以定义一个通用的类GenericClass,把所有的其它类指针及成员函数指针转换为GenericClass类指针及它的成员函数指针。因此我们需要改造为下面的function

[cpp]  view plain copy
  1. class GenericClass {};  
  2.   
  3. template<class T> class function;    
  4. template<typename R, typename T>    
  5. class  function<R (T::*)() >     
  6. {    
  7. public:    
  8.     typedef R(GenericClass::*fun)();    
  9.     function (fun p, T* pthis):m_ptr(p), m_pThis(pthis){}    
  10.   
  11.     R operator()()      {(m_pThis->*m_ptr)();}    //!!
  12.     fun m_ptr;    
  13.     GenericClass* m_pThis;    
  14. };    

接下来,需要一个转换函数,把对应的类指针和其成员函数指针转换为GenericClass的指针。这一个转换函数需要写成一个模板函数,以便对应不同的成员函数指针有不同的转换算法。

[cpp]  view plain copy
  1. template <int N>  
  2. struct SimplifyMemFunc   
  3. {    template <class X, class XFuncType, class GenericMemFuncType>  
  4.     inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind,   
  5.         GenericMemFuncType &bound_func) { }  
  6. };  

上面的实现有两处用到了模板参数,第一处是SimplifyMemFunc的模板参数,它让不同sizeof的函数指针用到不同的SimplifyMemFunc类(不同类型而已)。第二处是Convert方法,使得它有能力接受不同类型的this指针和成员函数指针,Convert方法的三个参数分别代表转换前的this指针,转换前的成员函数指针,转换后的通用成员函数指针,Convert的返回是转换后的通用this指针。

对于成员函数指针大小只有4的时候,转换函数如下:

[cpp]  view plain copy
  1. const int SINGLE_MEMFUNCPTR_SIZE = sizeof(void (GenericClass::*)());  
  2. template <>  
  3. struct SimplifyMemFunc<SINGLE_MEMFUNCPTR_SIZE>  
  4. {  
  5.     template <class X, class XFuncType, class GenericMemFuncType>  
  6.     inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind,   
  7.         GenericMemFuncType &bound_func)  
  8.     {  
  9.             bound_func = reinterpret_cast<GenericMemFuncType>(function_to_bind);  
  10.             return reinterpret_cast<GenericClass *>((X*)pthis);  
  11.     }  
  12. };  
这时只需要强转指针和函数指针。


当成员函数指针大小是8的时候,情况要复杂一些。这时,需要把this指针的偏移加上,再进行强转

[cpp]  view plain copy
  1. template<>  
  2. struct SimplifyMemFunc< SINGLE_MEMFUNCPTR_SIZE + sizeof(int) >  {  
  3.     template <class X, class XFuncType, class GenericMemFuncType>  
  4.     inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind,   
  5.         GenericMemFuncType &bound_func) {   
  6.             union {  
  7.                 XFuncType func;  
  8.                 struct {       
  9.                     GenericMemFuncType funcaddress; // points to the actual member function  
  10.                     int delta;       // #BYTES to be added to the 'this' pointer  
  11.                 }s;  
  12.             } u;  
  13.             u.func = function_to_bind;  
  14.             bound_func = u.s.funcaddress;  
  15.             return reinterpret_cast<GenericClass *>(reinterpret_cast<char *>(pthis) + u.s.delta);   
  16.     }  
  17. };  
根据前面的推理,当成员函数指针大小为8的时候,后4个字节存放的是this指针的偏移,因此,这种情况下,构造了一个联合体将成员函数指针解析出来,分成函数地址和偏移两部分。再将偏移加在this上,作为转换后的通用this指针。


当成员函数指针大小是12的时候,情况最为复杂,已经不是单纯地加上一个偏移就能解决的了,这需要查一次虚基偏移表,虽然我们可以用代码写一串加法运算完成这一点,但这样太不优雅了,比较优雅一点的方法就是模拟一次通用类的调用,让编译器替我们去算这个偏移

[cpp]  view plain copy
  1. struct MicrosoftVirtualMFP {  
  2.     void (GenericClass::*codeptr)(); // points to the actual member function  
  3.     int delta;      // #bytes to be added to the 'this' pointer  
  4.     int vtable_index; // or 0 if no virtual inheritance  
  5. };  
  6.   
  7. struct GenericVirtualClass : virtual public GenericClass  
  8. {  
  9.     typedef GenericVirtualClass * (GenericVirtualClass::*ProbePtrType)();  
  10.     GenericVirtualClass * GetThis() { return this; }  
  11. };  
  12.   
  13. template <>  
  14. struct SimplifyMemFunc<SINGLE_MEMFUNCPTR_SIZE + 2*sizeof(int) >  
  15. {  
  16.   
  17.     template <class X, class XFuncType, class GenericMemFuncType>  
  18.     inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind,   
  19.         GenericMemFuncType &bound_func) {  
  20.             union {  
  21.                 XFuncType func;  
  22.                 GenericClass* (X::*ProbeFunc)();  
  23.                 MicrosoftVirtualMFP s;  
  24.             } u;  
  25.             u.func = function_to_bind;  
  26.             bound_func = reinterpret_cast<GenericMemFuncType>(u.s.codeptr);  
  27.             union {  
  28.                 GenericVirtualClass::ProbePtrType virtfunc;  
  29.                 MicrosoftVirtualMFP s;  
  30.             } u2;  
  31.   
  32.             u2.virtfunc = &GenericVirtualClass::GetThis;  
  33.             u.s.codeptr = u2.s.codeptr;  
  34.             return (pthis->*u.ProbeFunc)();  
  35.     }  
  36. };  

这里构造函数指针u和u2,u指向的就是传入的函数指针。u2指向的是&GenericVirtualClass::GetThis。这时,把u2的函数地址赋值给u.u的指针结构就产生了如图的变化。


这时再对this指针调用这个u函数指针,就达到了偏移的效果


下面给出全部的代码:

[cpp]  view plain copy
  1. class B1    
  2. {    
  3. publicvirtual void fb1()  
  4.         {  
  5.             printf("fb1");  
  6.         }  
  7. };    
  8. class B2    
  9. {  
  10. public:  
  11.     virtual void fb2(){}  
  12. };    
  13. class D1 : public B2, public B1    
  14. {  
  15. publicvirtual void fb1()  
  16.         {  
  17.             printf("fb2");  
  18.         }  
  19. };    
  20. class D2 : virtual public B1    
  21. {    
  22. publicvirtual void fb1()  
  23.         {  
  24.             printf("d2fb1");  
  25.         }  
  26. };   
  27.   
  28. class GenericClass {};  
  29.   
  30. template<class T> class function;    
  31. template<typename R, typename T>    
  32. class  function<R (T::*)() >     
  33. {    
  34. public:    
  35.     typedef R(GenericClass::*fun)();    
  36.     function (fun p, T* pthis):m_ptr(p), m_pThis(pthis){}    
  37.   
  38.     R operator()()      {(m_pThis->*m_ptr)();}    
  39.     fun m_ptr;    
  40.     GenericClass* m_pThis;    
  41. };    
  42.   
  43. template <int N>  
  44. struct SimplifyMemFunc   
  45. {  
  46.     template <class X, class XFuncType, class GenericMemFuncType>  
  47.     inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind,   
  48.         GenericMemFuncType &bound_func) { }  
  49. };  
  50.   
  51.   
  52. const int SINGLE_MEMFUNCPTR_SIZE = sizeof(void (GenericClass::*)());  
  53. template <>  
  54. struct SimplifyMemFunc<SINGLE_MEMFUNCPTR_SIZE>  
  55. {  
  56.     template <class X, class XFuncType, class GenericMemFuncType>  
  57.     inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind,   
  58.         GenericMemFuncType &bound_func)  
  59.     {  
  60.             bound_func = reinterpret_cast<GenericMemFuncType>(function_to_bind);  
  61.             return reinterpret_cast<GenericClass *>(pthis);  
  62.     }  
  63. };  
  64.   
  65.   
  66. template<>  
  67. struct SimplifyMemFunc< SINGLE_MEMFUNCPTR_SIZE + sizeof(int) >  {  
  68.     template <class X, class XFuncType, class GenericMemFuncType>  
  69.     inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind,   
  70.         GenericMemFuncType &bound_func) {   
  71.             union {  
  72.                 XFuncType func;  
  73.                 struct {       
  74.                     GenericMemFuncType funcaddress; // points to the actual member function  
  75.                     int delta;       // #BYTES to be added to the 'this' pointer  
  76.                 }s;  
  77.             } u;  
  78.             u.func = function_to_bind;  
  79.             bound_func = u.s.funcaddress;  
  80.             return reinterpret_cast<GenericClass *>(reinterpret_cast<char *>(pthis) + u.s.delta);   
  81.     }  
  82. };  
  83.   
  84.   
  85.   
  86.   
  87. struct MicrosoftVirtualMFP {  
  88.     void (GenericClass::*codeptr)(); // points to the actual member function  
  89.     int delta;      // #bytes to be added to the 'this' pointer  
  90.     int vtable_index; // or 0 if no virtual inheritance  
  91. };  
  92.   
  93. struct GenericVirtualClass : virtual public GenericClass  
  94. {  
  95.     typedef GenericVirtualClass * (GenericVirtualClass::*ProbePtrType)();  
  96.     GenericVirtualClass * GetThis() { return this; }  
  97. };  
  98.   
  99. template <>  
  100. struct SimplifyMemFunc<SINGLE_MEMFUNCPTR_SIZE + 2*sizeof(int) >  
  101. {  
  102.   
  103.     template <class X, class XFuncType, class GenericMemFuncType>  
  104.     inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind,   
  105.         GenericMemFuncType &bound_func) {  
  106.             union {  
  107.                 XFuncType func;  
  108.                 GenericClass* (X::*ProbeFunc)();  
  109.                 MicrosoftVirtualMFP s;  
  110.             } u;  
  111.             u.func = function_to_bind;  
  112.             bound_func = reinterpret_cast<GenericMemFuncType>(u.s.codeptr);  
  113.             union {  
  114.                 GenericVirtualClass::ProbePtrType virtfunc;  
  115.                 MicrosoftVirtualMFP s;  
  116.             } u2;  
  117.   
  118.             u2.virtfunc = &GenericVirtualClass::GetThis;  
  119.             u.s.codeptr = u2.s.codeptr;  
  120.             return (pthis->*u.ProbeFunc)();  
  121.     }  
  122. };  
  123.   
  124. template<typename R, typename T, typename X>  
  125. function<R (GenericClass::*)() >  MakeDelegate(T* pthis, R (X::*mem_fun)())  
  126. {  
  127.     typedef R (GenericClass::*GenericMemFuncType) ();  
  128.   
  129.     GenericMemFuncType pGenFun;  
  130.     GenericClass* pGenThis = SimplifyMemFunc<sizeof(mem_fun)>::Convert(pthis, mem_fun, pGenFun);  
  131.   
  132.       
  133.     return function<R (GenericClass::*)() >(pGenFun, pGenThis);  
  134. }  
  135.   
  136. int _tmain(int argc, _TCHAR* argv[])  
  137. {  
  138.     B1 b1;  
  139.     D1 d1;  
  140.     D2 d2;  
  141.   
  142.     function<void (GenericClass::*)() > f1 = MakeDelegate(&b1, &B1::fb1);  
  143.     function<void (GenericClass::*)() > f2 = MakeDelegate(&d1, &D1::fb1);  
  144.     function<void (GenericClass::*)() > f3 = MakeDelegate(&d2, &D2::fb1);  
  145.       
  146.     f1();  
  147.     f2();  
  148.     f3();  
  149.     return 0;  
  150. }  


 与boost的效率比较

下载了FastDelegate库,将它与boost运行效率作比较,发现效率提升了1倍多。

[cpp]  view plain copy
  1. D2* pd21 = new D2;  
  2. boost::function<int ()> bf = boost::bind(&D2::fb1, pd21);  
  3. for (int i = 0; i < 100000000; ++i)  
  4. {  
  5. bf();           // 1392 ms  
  6. }  
  7. D2* pd22 = new D2;  
  8. for (int i = 0; i < 100000000; ++i)  
  9. {  
  10. pd22->fb1();     // 726 ms  
  11. }  
  12. D2* pd23 = new D2;  
  13. fastdelegate::FastDelegate0<int> MyDelegate;  
  14. MyDelegate.bind(pd23, &D2::fb1);  
  15. for (int i = 0; i < 100000000; ++i)  
  16. {  
  17. MyDelegate();    // 551ms  
  18. }  
当然,一个应用程序的瓶颈绝不应在此,这里只是提出了一种在编译期优化问题的思想,可以借鉴学习。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值