C++中如何将类的非静态成员函数绑定到函数指针上(函数对象、函数指针)
回调函数,在函数式编程和异步编程等中应用非常广泛,C++11开始,通过std::function, std::bind, Lamda等手段,我们可以很方便的实现回调函数的注册,举例如下:
#include <function> using ProcessCallback = std::function<int(int, int)>; void register_with_library(ProcessCallback callback_func) { int x = 0; int y = 1; printf("%d\n",callback_func(x, y)); } int FuncTest(int a, int b) { return a + b; } struct CallBackClass { int FuncTest(int k, int j); }; int CallBackClass::FuncTest(int k, int j) { return k - j; } int main() { register_with_library(&FuncTest); //例1 CallBackClass cbc; register_with_library(std::bind(&CallBackClass::FuncTest,&cbc, std::placeholders::_1, std::placeholders::_2)); //例2 return 0; }
其中register_with_library函数以一个std::function生成的签名为int(int,int)函数对象作为参数,我们可以直接把普通函数和静态函数绑定到这个函数对象上(例1),
针对类的一般成员函数,由于有隐含的this指针存在导致函数签名不一致,此时可以通过std::bind方法绑定this指针后生成一个std::bind对象,并可以直接转换为对于的函数对象(例2),其中std::placeholders::_1、_2...为占位符,表示接收对应数量的参数。
但是针对C-Style的接口,回调函数往往是以函数指针的形式给出,此时会有什么不同呢
typedef int (*ProcessCallbackT)(int, int); void register_with_library(ProcessCallbackT func) { int x = 0; int y = 1; printf("Value: %i\n", func(x, y)); }
修改原来例子中需要接收回调的函数(暂不考虑函数调用时的参数压栈方式)
此时,无法再通过函数对象的方式来注册回调函数,该接口只能接收普通函数、类的静态成员函数以及无捕获的Lamda做为参数
那么是否还能够将类的一般成员函数绑定到其中呢,这里有一个实现方式可以参考
typedef int (*ProcessCallbackT)(int, int); void register_with_libraryT(ProcessCallbackT func) { int x = 0; int y = 1; printf("Value: %i\n", func(x, y)); } function<int(int, int)> function_obj; int FuncHelper(int a, int b) { return function_obj(a, b); } int main() { register_with_libraryT(FuncTest); CallBackClass cbc; // register_with_library(std::bind(&CallBackClass::FuncTest,&cbc, std::placeholders::_1, std::placeholders::_2)); function_obj = std::bind(&CallBackClass::FuncTest, &cbc, std::placeholders::_1, std::placeholders::_2); register_with_libraryT(FuncHelper); return 0; }
上例中使用一个普通函数FuncHelper包裹了类成员函数的函数对象,从而达到预想的效果,更进一步,可以通过一个静态类来让实现更加优雅
using ProcessCallback = std::function<int(int, int)>; class FuncHelperClass { static inline ProcessCallback function_obj; public: static int FuncHelper(int a, int b) { return function_obj(a, b); } static void FuncBind(ProcessCallback callback) { function_obj = callback; } }; typedef int (*ProcessCallbackT)(int, int); void register_with_libraryT(ProcessCallbackT func) { int x = 0; int y = 1; printf("Value: %i\n", func(x, y)); } nt main() { FuncHelperClass::FuncBind(std::bind(&CallBackClass::FuncTest, &cbc, std::placeholders::_1, std::placeholders::_2)); register_with_libraryT(FuncHelperClass::FuncHelper); return 0; }
注意:static inline 成员必须要C++17支持,否则你需要再定义一下静态成员function_obj
如果我们想进一步扩展到一般情况,那就加入模板吧
template <typename T> struct Callback; //特例化 template <typename Ret, typename... Params> struct Callback<Ret(Params...)> { template <typename... Args> static Ret callback(Args... args) //对应静态成员函数 { return func(args...); } static inline std::function<Ret(Params...)> func; //对应静态函数对象 }; typedef int (*callback_t)(int, int); class YourCallBackClass { public: YourCallBackClass(); int YourFunc(int k, int j); //你要的回调函数,任意定义 }; YourCallBackClass::YourCallBackClass() { Callback<int(int, int)>::func = std::bind(&YourCallBackClass::YourFunc, this, std::placeholders::_1, std::placeholders::_2); callback_t func = static_cast<callback_t>(Callback<int(int, int)>::callback); register_with_libraryT(func); //此时func是函数指针,可以注册到对应的回调处 } int YourCallBackClass::YourFunc(int k, int j) { return k - j; } int main() { YourCallBackClass ycbc; return 0; }
代码参考:https://stackoverflow.com/questions/1000663/using-a-c-class-member-function-as-a-c-callback-function
但是例中的模板还是存在一系列问题:
1、同一个类的多个对象,只有最后绑定的能计算出正确结果
2、函数签名相同的回调函数被认为是同一个函数对象,只有最后绑定的有效
其中针对问题1作者建议通过thread_local关键字,针对每个线程持有一个对象的情况做出处理,但是问题2没有提到,可以通过将类名作为模板参数的方法来实现针对不同类的不同模板,具体内容如下:
class YourCallBackClass { public: YourCallBackClass(); int YourFunc(int k, int j); //你要的回调函数,任意定义 int times; }; int YourCallBackClass::YourFunc(int k, int j) { return (k - j)*times; } class YourCallBackClass2 { public: YourCallBackClass2(); int YourFunc(int k, int j); //你要的回调函数,任意定义 }; int YourCallBackClass2::YourFunc(int k, int j) { return k - 1000*j; }
如上例中,由于YourCallBackClass2和YourCallBackClass中 YourFunc的函数签名相同,因此会注册到同一个模板类上,那么后注册的会覆盖先注册的,通过引入类名的方式,对不同类做出区别对待:
template <typename cls, typename T> //模板参数加入类标志 struct Callback { }; //特例化 template <typename cls, typename Ret, typename... Params> struct Callback<cls, Ret(Params...)> { template <typename... Args> static Ret callback(Args... args) { return func(args...); } static inline std::function<Ret(Params...)> func; }; typedef int (*callback_t)(int, int); class YourCallBackClass { public: YourCallBackClass(); int YourFunc(int k, int j); int times; }; YourCallBackClass::YourCallBackClass() { Callback<YourCallBackClass,int(int, int)>::func = std::bind(&YourCallBackClass::YourFunc, this, std::placeholders::_1, std::placeholders::_2);//实例化时传入类名 callback_t func = static_cast<callback_t>(Callback<YourCallBackClass,int(int, int)>::callback); register_with_libraryT(func); } int YourCallBackClass::YourFunc(int k, int j) { return (k - j)*times; } class YourCallBackClass2 { public: YourCallBackClass2(); int YourFunc(int k, int j); }; YourCallBackClass2::YourCallBackClass2() { Callback<YourCallBackClass2, int(int, int)>::func = std::bind(&YourCallBackClass2::YourFunc, this, std::placeholders::_1, std::placeholders::_2); //实例化时传入类名 callback_t func = static_cast<callback_t>(Callback<YourCallBackClass2,int(int, int)>::callback); register_with_libraryT(func); } int YourCallBackClass2::YourFunc(int k, int j) { return k - 1000*j; }
针对多个对象的问题,如果能在编译器确定也可以通过模板特例化来解决,当然如果你不想再麻烦,直接使用FuncHelperClass方法中所述,对多个对象进行各自独立的处理也能达到功能。