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方法中所述,對多個對象進行各自獨立的處理也能達到功能。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM