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方法中所述,对多个对象进行各自独立的处理也能达到功能。
浙公网安备 33010602011771号