回調函數,在函數式編程和異步編程等中應用非常廣泛,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方法中所述,對多個對象進行各自獨立的處理也能達到功能。