很多时候C语言的类库,如libcurl等,会提供setCallBack函数进行设置,函数要求传入一个C语言风格的函数指针。适当的时候进行回调。
Libcurl里面回调函数就要求有如下形式的参数。
size_t function( void *ptr, size_t size, size_t nmemb, void *userdata)
其他的类库中间也有很多类似的要求。
这个要求在c里面相对容易,但是如果一个C++的类成员的成员函数进行回调时,会出现两个问题。
第一个是C++中的成员函数指针和c中的函数指针是两个不同的类型,不能相互转换。
第二个是C++中的成员函数指针需要类的对象才能进行函数调用,而c语言中的callback完全不允许这样做。
在C++中,这两个类型可以强制类型转换。一个 void F::foo(void * a)会变成void foo(F* this , void *a),如果要求的调用顺序和这个顺序正好一致,那么这个问题就很好解决了。详见这个blog
如果约定的函数和强制转换出来的函数不同,就需要一个包装函数进行包装了。
vc大大先给出来一个简单的解:
void TheCallback(void* parameter)
{
RealObject* o=(RealObject*)parmaeter;
o->RealCallback();
}
当然,如果用这个方法的话,每一个类的每一个callback都需要一个类似的函数。肯定需要一种方法来进行简化。询问vc,vc一直处于离线 打游戏的状态。d大大给了一个基于functor的方法
template<typename T>
class Functor {
public:
typedef void (T::*Func)(void*);
Functor(T* ob, Func f);
void setData(void* data) { mData = data; }
void operator()() {
(mObj->mFunc)(mData);
}
T* mObj;
Func mFunc;
void* mData;
};
template<typename T>
void generic_callback(void* functor) {
Functor<T>* f = (Functor<T>*)functor;
(*f)();
}
void setCallback(void (*f)(void*), void* data) {
f(data);
}
int main()
{
MyClass instance;
Functor<MyClass> functor(&instance, &MyClass::test);
setCallback(&generic_callback<MyClass>, &functor);
}
当时在想问题的时候完全没有想到可以利用函数成员指针来进行泛型,进行n多尝试之后,有了下面的方法
template <typename T , void (T::*func)(void *)>
void generalCallback(void *data , void *udata)
{
T *ptr = (T*)udata;
(ptr->*func)(data);
}
在调用的时候之需要setCallback(generalCallback<T , T::func> , &Obj)就可以完成函数的包装工作。
错误和一个很奇怪的bug
因为这样的方法还不是很完美,从而想出了通过静态变量存储对象指针的方法,但是这个方法是完全错误的。写了下面一段代码:
callback( (ptrSaver<A>(&a) , generalCallback<A , &A::bar>) , str , null);
通过ptrSaver保存对象的指针,然后调用的时候就不用传递对象指针了。但是由于static的全局性,这样不能保证调用的结果是想要的结果。
奇怪的是,这段代码在vs c++2012和clang上面都能编译通过,但是在gcc上面却一直提示存在“insufficient contextual information to determine type”错误,不太清楚原因。
小节
这个问题本质上是因为C语言中的函数是没有状态的,不能形成闭包。从而无法携带类的信息,也就无法做到调用成员函数。
TODO:利用C++ 11的lambda函数找出一个比较优雅的方法。