【转】回调函数中如何调用类中的非静态成员变量或非静态成员函数
回调函数中调用类中的非静态成员变量或非静态成员函数
【问题1】如何在类中封装回调函数?
【答】:
a.回调函数只能是全局的或是静态的。
b.全局函数会破坏类的封装性,故不予采用。
c.静态函数只能访问类的静态成员,不能访问类中非静态成员。
【问题2】如何让静态函数访问类的非静态成员?
【解决方案】:
声明一静态函数a(),将类实例对象指针做为参数传入。如:
class A()
{
static void a(A *); //静态函数
void b(); //非静态函数
}
void A::a(A * pThis)
{
pThis->b(); //静态函数中调用非静态函数
}
【问题3】回调函数中如何访问非静态成员?
由于回调函数往往有固定定义,并不接受 A * pThis 参数
如:CALLBACK MyTimerProc(HWND hwnd,UINT uMsg,UINT idEvent,DWORD dwTime);
【解决方案1】:本方案当遇到有多个类实例对象时会有问题。原因是pThis指针只能指向一个对象。
class A()
{
static void a(); //静态回调函数
void b(); //非静态函数
static A * pThis; //静态对象指针
}
A * A::pThis=NULL;
A::A() //构造函数中将this指针赋给pThis,使得回调函数能通过pThis指针访问本对象
{
pThis=this;
}
void A::a()
{
if (pThis==NULL) return;
pThis->b(); //回调函数中调用非静态函数
}
【解决方案2】:本方案解决多个类实例对象时方案1的问题。用映射表存所有对象地址,每个对象保存自己的ID号。
typedef CMap<UINT,UINT,A*,A*> CAMap;
class A()
{
static void a(); //静态回调函数
void b(); //非静态函数
int m_ID; //本对象在列表中的ID号
static int m_SID; //静态当前对象ID (需要时,将m_ID赋值给m_SID以起到调用本对象函数的功能)
static CAMap m_Map; //静态对象映射表
}
CAMap A::m_Map;
int A::m_SID=0;
A::A() //构造函数中将this指针赋给pThis,使得回调函数能通过pThis指针访问本对象
{
if(m_Map.IsEmpty())
{
m_ID=1;
}
else
{
m_ID=m_Map.GetCount()+1;
}
m_Map.SetAt( m_ID, this );
}
void A::a()
{
if (m_Map.IsEmpty()) return;
A * pThis=NULL;
if(m_Map.Lookup(m_SID,pThis))
{
pThis->b(); //回调函数中调用非静态函数
};
}
=================================
C++中类成员函数作为回调函数
回调函数是基于C编程的Windows SDK的技术,不是针对C++的,程序员可以将一个C函数直接作为回调函数,但是如果试图直接使用C++的成员函数作为回调函数将发生错误,甚至编译就不能通过。
普通的C++成员函数都隐含了一个传递函数作为参数,亦即“this”指针,C++通过传递一个指向自身的指针给其成员函数从而实现程序函数可以访问C++的数据成员。这也可以理解为什么C++类的多个实例可以共享成员函数但是确有不同的数据成员。由于this指针的作用,使得将一个CALLBACK型的成员函数作为回调函数安装时就会因为隐含的this指针使得函数参数个数不匹配,从而导致回调函数安装失败。
这样从理论上讲,C++类的成员函数是不能当作回调函数的。但我们在用C++编程时总希望在类内实现其功能,即要保持封装性,如果把回调函数写作普通函数有诸多不便。经过网上搜索和自己研究,发现了几种巧妙的方法,可以使得类成员函数当作回调函数使用。
这里采用Linux C++中线程创建函数pthread_create举例,其原型如下:
- int pthread_create( pthread_t *restrict tidp , const pthread_attr_t *restrict attr , void* (*start_rtn)(void*) , void *restrict arg );
- class MyClass
- {
- pthread_t TID;
- public:
- void func()
- {
- //子线程执行代码
- }
- bool startThread()
- {//启动子线程
- int ret = pthread_create( &TID , NULL , callback , this );
- if( ret != 0 )
- return false;
- else
- return true;
- }
- };
- static void* callback( void* arg )
- {//回调函数
- ((MyClass*)arg)->func();调用成员函数
- return NULL;
- }
- int main()
- {
- MyClass a;
- a.startThread();
- }
类MyClass需要在自己内部开辟一个子线程来执行成员函数func()中的代码,子线程通过调用startThread()成员函数来启动。这里将回调函数callback写在了类外面,传递的参数是一个指向MyClass对象的指针(在pthrad_create()中由第4个参数this指定),回调函数经过强制转换把void*变为MyClass*,然后再调用arg->func()执行子线程的代码。
这样做的原理是把当前对象的指针当作参数先交给一个外部函数,再由外部函数调用类成员函数,以外部函数作为回调函数,但执行的是成员函数的功能,这样相当于在中间作了一层转换。缺点是回调函数在类外,影响了封装性,这里把callback()限定为static,防止在其它文件中调用此函数。
方法二:回调函数为类内静态成员函数,在其内部调用成员函数
在方法一上稍作更改,把回调函数搬到类MyClass里,这样就保持了封装性。代码如下:
- class MyClass
- {
- static MyClass* CurMy;//存储回调函数调用的对象
- static void* callback(void*);//回调函数
- pthread_t TID;
- void func()
- {
- //子线程执行代码
- }
- void setCurMy()
- {//设置当前对象为回调函数调用的对象
- CurMy = this;
- }
- public:
- bool startThread()
- {//启动子线程
- setCurMy();
- int ret = pthread_create( &TID , NULL , MyClass::callback , NULL );
- if( ret != 0 )
- return false;
- else
- return true;
- }
- };
- MyClass* MyClass::CurMy = NULL;
- void* MyClass::callback(void*)
- {
- CurMy->func();
- return NULL;
- }
- int main()
- {
- MyClass a;
- a.startThread();
- }
这个方法的好处时封装性得到了很好的保护,MyClass对外只公开一个接口startThread(),子线程代码和回调函数都被设为私有,外界不可见。另外没有占用callback的参数,可以从外界传递参数进来。但每个对象启动子线程前一定要注意先调用setCurMy()让CurMy正确的指向自身,否则将为其它对象开启线程,这样很引发很严重的后果。
方法三:对成员函数进行强制转换,当作回调函数
代码如下:
- class MyClass
- {
- pthread_t TID;
- void func()
- {
- //子线程执行代码
- }
- public:
- bool startThread()
- {//启动子线程
- typedef void* (*FUNC)(void*);//定义FUNC类型是一个指向函数的指针,该函数参数为void*,返回值为void*
- FUNC callback = (FUNC)&MyClass::func;//强制转换func()的类型
- int ret = pthread_create( &TID , NULL , callback , this );
- if( ret != 0 )
- return false;
- else
- return true;
- }
- };
- int main()
- {
- MyClass a;
- a.startThread();
- }
方法三的封装性比方法二更好,因为不涉及多个对象共用一个静态成员的问题,每个对象可以独立地启动自己的线程而不影响其它对象。