饭中淹的避难所~~~~~

偶尔来避难的地方~

  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  94 随笔 :: 0 文章 :: 257 评论 :: 0 Trackbacks

这个方法可以实现按照统一的接口来调用类成员函数,或者静态函数和非类成员函数. 主要原理很简单, 就是保存类对象指针和函数指针, 需要调用的时候就根据类对象指针是否为空来使用不同的方式调用函数.


首先, 我们需要一个把成员函数指针转化成void *的东西..(强制转换似乎是不行的), 因为我们需要把成员函数指针保存起来, 又不想让用户写函数指针类型描述, 那只能转换成void*比较方便.
这里我们使用 联合地址转换 的方法.

template <typename T1, typename T2>
struct _T2T{
 union {
  T1  _tv1;
  T2  _tv2;
 };
};

template <typename T1, typename T2>
inline T1 t2t( T2 tv2 )
{
 typedef struct _T2T<T1, T2> * PT2T;
 PT2T pt = (PT2T)&tv2;
 return pt->_tv1;
}

转换方法就是 t2t<void*>( &ClassName::FuncName );

然后, 我们来构造一个可以用 this 指针和 函数指针来进行函数调用的东西.....这里使用了我之前随笔里介绍的多参数定义宏, 用来生成多参数的函数调用.

struct callfunction
{
 class nullclass{};

 template <typename TRet>
 static inline TRet callauto( void * pThis, void * pFunc ){
  if( pThis == NULL )
   return callnothis<TRet>( pFunc );
  return callwiththis<TRet>( pThis, pFunc );
 }

#define DEFINE_CALLFUNC_AUTO_WITH_PARAM( paramcount ) template <typename TRet, DP_STMP_##paramcount(typename, Tp)>\
 static inline TRet callauto( void * pThis, void * pFunc, DP_MTMP_##paramcount(Tp, p ) ){\
  if( pThis == NULL )\
   return callnothis<TRet>( pFunc, LP_SNMP_##paramcount( p ) );\
  else\
   return callwiththis<TRet>( pThis, pFunc, LP_SNMP_##paramcount( p ) );\
 }
DEFINE_CALLFUNC_AUTO_WITH_PARAM(1);
DEFINE_CALLFUNC_AUTO_WITH_PARAM(2);
DEFINE_CALLFUNC_AUTO_WITH_PARAM(3);
DEFINE_CALLFUNC_AUTO_WITH_PARAM(4);
DEFINE_CALLFUNC_AUTO_WITH_PARAM(5);
DEFINE_CALLFUNC_AUTO_WITH_PARAM(6);
DEFINE_CALLFUNC_AUTO_WITH_PARAM(7);
DEFINE_CALLFUNC_AUTO_WITH_PARAM(8);
DEFINE_CALLFUNC_AUTO_WITH_PARAM(9);
DEFINE_CALLFUNC_AUTO_WITH_PARAM(10);

 template <typename TRet>
 static inline TRet callwiththis( void * pThis, void * pFunc ){
  typedef TRet (nullclass::*funcptr)();
  return (((nullclass*)pThis)->*p2t<funcptr>(pFunc))();
 }

 


#define DEFINE_CALLFUNC_WITHTHIS_WITH_PARAM(paramcount) template <typename TRet, DP_STMP_##paramcount(typename, Tp)>\
 static inline TRet callwiththis( void * pThis, void * pFunc, DP_MTMP_##paramcount(Tp, p ) ){\
  typedef TRet (nullclass::*funcptr)(DP_MTMP_##paramcount(Tp, p ));\
  return (((nullclass*)pThis)->*p2t<funcptr>(pFunc))(LP_SNMP_##paramcount( p ));\
 }
DEFINE_CALLFUNC_WITHTHIS_WITH_PARAM(1);
DEFINE_CALLFUNC_WITHTHIS_WITH_PARAM(2);
DEFINE_CALLFUNC_WITHTHIS_WITH_PARAM(3);
DEFINE_CALLFUNC_WITHTHIS_WITH_PARAM(4);
DEFINE_CALLFUNC_WITHTHIS_WITH_PARAM(5);
DEFINE_CALLFUNC_WITHTHIS_WITH_PARAM(6);
DEFINE_CALLFUNC_WITHTHIS_WITH_PARAM(7);
DEFINE_CALLFUNC_WITHTHIS_WITH_PARAM(8);
DEFINE_CALLFUNC_WITHTHIS_WITH_PARAM(9);
DEFINE_CALLFUNC_WITHTHIS_WITH_PARAM(10);

 template <typename TRet>
 static inline TRet callnothis( void * pFunc ){
  typedef TRet (*funcptr)();
  return (*p2t<funcptr>(pFunc))();
 }

#define DEFINE_CALLFUNC_NOTHIS_WITH_PARAM(paramcount) template <typename TRet, DP_STMP_##paramcount(typename, Tp)>\
 static inline TRet callnothis( void * pFunc, DP_MTMP_##paramcount(Tp, p ) ){\
  typedef TRet (*funcptr)(DP_MTMP_##paramcount(Tp, p ));\
  return (*p2t<funcptr>(pFunc))(LP_SNMP_##paramcount( p ));\
 }
DEFINE_CALLFUNC_NOTHIS_WITH_PARAM(1);
DEFINE_CALLFUNC_NOTHIS_WITH_PARAM(2);
DEFINE_CALLFUNC_NOTHIS_WITH_PARAM(3);
DEFINE_CALLFUNC_NOTHIS_WITH_PARAM(4);
DEFINE_CALLFUNC_NOTHIS_WITH_PARAM(5);
DEFINE_CALLFUNC_NOTHIS_WITH_PARAM(6);
DEFINE_CALLFUNC_NOTHIS_WITH_PARAM(7);
DEFINE_CALLFUNC_NOTHIS_WITH_PARAM(8);
DEFINE_CALLFUNC_NOTHIS_WITH_PARAM(9);
DEFINE_CALLFUNC_NOTHIS_WITH_PARAM(10);
};

这里面提供了三种调用方式, callauto 是根据this是否为NULL自动选择按照类成员函数, 还是普通函数 来调用, callwiththis 固定按照类成员函数来调用, callnothis 固定按照非成员函数的方式来调用.
每种调用方式提供了10个带参数的调用和1个不带参数的调用. 最大支持 10个参数的成员函数调用, 基本上已经足够了.

这个struct的使用方法是

callfunction::callwiththis<returntype>( objectptr, memfuncptr, params ... );

比如我们要调用 类CAddObject 的对象指针 pObject 的返回类型为int 名字为 add, 并且带有两个int型参数的成员函数, 我们只需要这样调用
int result = callfunction::callwiththis<int>( pObject, t2t<void*>( &CAddObject::add ), 20, 20 );
这样,我们就调用了这个函数,并且把结果保存在result

最后, 我们来封装一个函数调用的对象, 用来更方便的使用这个方法
template <typename TRet>
class CCustomCall
{
 typedef CCustomCall<TRet> TSelf;
 void * lpThis;
 void * lpFunc;
public:
 CCustomCall( const TSelf & sv ):lpThis(sv.lpThis), lpFunc(sv.lpFunc) {}
 template <typename TFunc>
 CCustomCall( void * _this, TFunc _f ):lpThis(_this){
  lpFunc = t2p<TFunc>( _f );
 }
 CCustomCall():lpThis(NULL), lpFunc(NULL){}

 TSelf & operator =( const TSelf & sv )
 {
  lpThis = sv.lpThis;
  lpFunc = sv.lpFunc;
  return (*this);
 }

#define DEFINE_CALL_WITH_PARAM(paramcount) template <DP_STMP_##paramcount( typename, Tp ) >\
 TRet call( DP_MTMP_##paramcount(Tp, p ) ){\
  return callfunction::callauto<TRet>( lpThis, lpFunc, LP_SNMP_##paramcount(p));\
 }
 DEFINE_CALL_WITH_PARAM(1);
 DEFINE_CALL_WITH_PARAM(2);
 DEFINE_CALL_WITH_PARAM(3);
 DEFINE_CALL_WITH_PARAM(4);
 DEFINE_CALL_WITH_PARAM(5);
 DEFINE_CALL_WITH_PARAM(6);
 DEFINE_CALL_WITH_PARAM(7);
 DEFINE_CALL_WITH_PARAM(8);
 DEFINE_CALL_WITH_PARAM(9);
 DEFINE_CALL_WITH_PARAM(10);
 TRet call(){return callfunction::callauto<TRet>( lpThis, lpFunc );}
bool empty(){ return (lpFunc == NULL);}
};

使用这个类, 就可以用简单的写法来实现调用各种函数...

比如下面演示了使用同一个类来实现成员函数和非成员函数的混合调用.

typedef CCustomCall<int> IntCall;

class CTest
{
public:
    int add( int n1, int n2 ){ return (n1+n2);}
    int mul( int n1, int n2 ){ return (n1*n2);}
};

int div( int n1, int n2 ){ return (n1/n2);}

int main(int argc, char * argv[])
{
    CTest test;
    IntCall addcall( &test, &CTest::add );
    IntCall mulcall( &test, &CTest::mul );
    IntCall divcall( NULL, &div );
    int nResult = 0;
    nResult = addcall.call( 20, 20 );
    printf( "addcall result = %d\n", nResult );
    nResult = mulcall.call( 20, 20 );
    printf( "mulcall result = %d\n", nResult );
    nResult = divcall.call( 20, 20 );
    printf( "divcall result = %d\n", nResult );
    return 0;
}
输出结果是

40
400
1

下面是一个作为事件调用的例子

typedef CCustomCall<void> EventCall;
class CButton
{
public:
    EventCall eventOnClick;
};

class CApplication
{
.....
    void OnOkButtonClick( CButton * pButton );
    CButton m_OkButton;
};

............................初始化事件...............
BOOL CApplication::Init(){
.....
m_OkButton.eventOnClick = EventCall( this, &CApplication::OnOkButtonClick );
...
}

............................在BUTTON的鼠标按下事件中.........................

void CButton::OnMouseDown( int key, int x, int y )
{
    if( key == VK_LBUTTON && !eventOnClick.empty() )
        eventOnClick.call( this );               /// 调用了设置的事件函数, 在这个例子里, 当this = CApplication::m_OkButton的时候, 就会调用 CApplication::OnOkButtonClick....
}


posted on 2007-04-23 14:44 饭中淹 阅读(3491) 评论(12)  编辑 收藏 引用

评论

# re: 通用的类成员函数调用方法. 2007-04-24 08:50 ChenA
不错,提点小意见,呵呵。
函数指针直接转void*?最好还是带类型检查。
最好不需要加一个自定义类型,比如typedef CCustomCall<int> IntCall,一个固定的类型就可以了,这样可以托管给任意类的任意函数。
call这个名字也不太好,直接重载()比较直接。  回复  更多评论
  

# re: 通用的类成员函数调用方法.[未登录] 2007-04-24 09:10 梦在天涯
一般可以用在什么地方?


还有如果我给的函数指针与我所传入的函数的参数个数或是类型不匹配,那结构又如何?
  回复  更多评论
  

# re: 通用的类成员函数调用方法.[未登录] 2007-04-24 12:07 饭中淹
@梦在天涯
@ChenA
这个方法的很大的缺点就是无法进行类型检测
带类型检测的我正在研究如何实现...
原来的实现这个功能的代码是以前用X86汇编写的,
刚修改成这种形式.
而且我刚学习模版的高级应用,请两位多指教.  回复  更多评论
  

# re: 通用的类成员函数调用方法. 2007-04-24 13:14 eXile
to 饭中淹 :
这个实际上是如何实现代理 (delegate) 的问题, codeproject 上有很多关于它的讨论.

to 梦在天涯 :
这个主要用于模块之间的解藕
  回复  更多评论
  

# re: 通用的类成员函数调用方法. 2007-04-24 13:44 eXile
发现一个很严重的错误: t2t 函数是错误的.
原因: 一个成员函数指针的大小可能4, 8, 12, 16 byte, 而32位平台sizeof(void*)= 4, t2t会造成截断.  回复  更多评论
  

# re: 通用的类成员函数调用方法. 2007-04-24 13:47 eXile
所以, 这些东西还是使用一些成熟的实现吧!  回复  更多评论
  

# re: 通用的类成员函数调用方法.[未登录] 2007-04-24 16:59 饭中淹
@eXile
成员函数指针在32位平台会超过32位么?  回复  更多评论
  

# re: 通用的类成员函数调用方法.[未登录] 2007-04-24 17:46 饭中淹
@eXile
又学到很多东西.......
这里面陷阱也很多啊~~做了这么多年都不知道....惭愧~~  回复  更多评论
  

# re: 通用的类成员函数调用方法.[未登录] 2007-04-24 17:52 饭中淹
@eXile
我看过CODEPROJECT上的DELEGATE的讨论.
我觉得实现一下才能亲身体会到里面的东西.
光用现成的东西, 感觉学不到东西了.

今天终于又知道, 类成员函数里面这么复杂的东西, 以前还奇怪多继承的THIS OFFSET的问题, 原来是这么的复杂...

看来这个方法是不能使用了.不过物有所值~~~  回复  更多评论
  

# re: 通用的类成员函数调用方法. 2007-04-26 11:05 ChenA
类型检测可以实现的,模板偏特化,呵呵,如果不匹配,编译时会报错。  回复  更多评论
  

# re: 通用的类成员函数调用方法. 2007-04-30 10:32 eXile
这个方法也不是不能使用,不过不要用void*, 要选择一个更大的类型。
如下:
class X;
typedef void (X::*MemFunPtr)(void);

使用MemFunPtr,这样就不会有截断了。  回复  更多评论
  

# re: 通用的类成员函数调用方法. 2007-04-30 10:36 eXile
上面的X不要有实现,这样才能保证MemFunPtr尺寸最大  回复  更多评论
  


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