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

posted on 2021-11-29 17:14  二十一级厨子  阅读(3516)  评论(2编辑  收藏  举报

导航