前言
最近工作需要想辦法優化架構,因此設計了兩個類別各自負責自己工作,但有些資訊是B類別動作後,A類別需要進行更新。
最直覺想到的寫法,是讓B物件認識A物件,但是這樣A物件就跟B物件產生了關聯性(耦合)。
第二種想到寫法,是A物件需要這個資訊的時候,檢查資訊有沒有更新。但這樣A物件需要多些判斷,且每個需要資訊的地方都需要加這個判斷式,後續維護不容易。
有沒有什麼辦法是,B不需要認識A,A也不需要一直檢查呢?
最後想到的解法是:使用Callback Function!!!!!
什麼是Callback Function?
一般Function是主動執行,比如說前言中提到最直覺的寫法,B物件呼叫A的方法,主動執行A更新的動作(或是可以想成是動態執行);至於Callback Function,則是B先記得A的更新Method的位址(指標),要動作時再呼叫這個函式指標對應到的Function(靜態執行)。
簡單的說,一般Call Function是直接呼叫Function,而Callback function則是記得function的位址,需要時再執行該Pointer指向的函式。
常見的Callback Function
Callback Function其實滿常見的,以下列出幾個可以看到Callback Function的API
- std::sort
- openCV MouseCallback
- gstreamer AppSink
Callback Function怎麼用?
Function Pointer
首先,先聊一下函式指標怎麼寫呢?
C++函式指標寫法如下:
void (*func )(int, int)
這一段翻譯成人類的語言就是:func是一個函式指標,這個指標指向的函式引數為兩個int,回傳值則為void。
不過為了使用上的便利性,通常會用typedef的方式給他名字,例如:
typedef void (*FUNC )(int, int)
後續就能宣告FUNC並給予別名。
宣告及使用Callback Function
先來一段範例程式碼
#include <iostream>
using namespace std;
typedef void (*CALLBACK)( int, int, void* );
class A{
public:
A( void );
// constructor
static void UpdateCallback( int, int, void* );
// callback function pointer pointed to
void PrintMember( void ) const;
// show the member
private:
int a;
int b;
};
class B{
public:
B( void );
// constructor
void RegisterCallback( CALLBACK, void* );
// register callback and the callee
void CallFunction( void );
// call by main
private:
CALLBACK pFunc;
void *m_pCallee;
};
A::A( void )
// constructor
{
a = 0;
b = 0;
}
void A::UpdateCallback( int inputA, int inputB, void *Self )
// callback function pointer pointed to
{
// cast user data to A
A* pA = (A*)Self;
// update with input value
pA->a = inputA;
pA->b = inputB;
}
void A::PrintMember( void ) const
// show the member
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
}
B::B( void )
// constructor
{
// do nothing
}
void B::RegisterCallback( CALLBACK FuncCB, void* pCallee )
// register callback and the callee
{
pFunc = FuncCB;
m_pCallee = pCallee;
}
void B::CallFunction( void )
// call by main
{
cout << "execute function pointer" << endl;
pFunc( 2, 3, m_pCallee );
}
int main( void )
{
A instanceA;
B instanceB;
// register callback to B
instanceB.RegisterCallback( instanceA.UpdateCallback, &instanceA );
cout << "Before run B::CallFunction: " << endl;
instanceA.PrintMember();
instanceB.CallFunction();
cout << "After run B::CallFunction: " << endl;
instanceA.PrintMember();
return 0;
}
從程式碼中可以看到,一開始要將A類別的UpdateCallback註冊到B類別,也就是告訴B類別Callback Function的位址以及該Callback function的使用者(Callee,傳進一個void pointer即可)。
要傳進物件A的pointer的原因是因為Callback Function中需要修改到A,如果沒有傳進A物件本身,那將無法直接修改到A物件中的值。(可能有其他更好的寫法,還要再研究)
另外要注意實作功能的Callback Function必須是static。
(註:C++11以後用function跟bind好像就可以不用宣告為static)
註冊Callback Function後,B物件執行CallFunction的時候,就會執行Callback指向的函式,也就是A的UpdateCallback,並將Callee傳入(在這個情境下是instanceA的pointer),在UpdateCallback中,會將void pointer轉型成class A的pointer,並將值填入A的pointer中。
可以發現class B並不需要知道class A(只需要傳入一個void pointer),也就是說透過Callback Function的機制,就達成B類別不需要認識A類別,就可以叫A做事情的目的!
另外以程式碼擴展性來說,Callback Function增加了更多彈性~如果我後續修改需求,有一個新的類別C,也是要進行類似的動作(比如說使用B傳入兩個int進行相加,再存進member),那我只要在C類別中實作Callback Function,然後註冊進B就可以了,完全不需要修改B。
結論
Callback Function的實用性很高,對於程式碼擴展性有很大的幫助。希望整理完後,之後可以用得更爐火純青~
另外也要找時間去熟悉C#的delegate,因為好像是類似的概念!
參考資料:
https://dangerlover9403.pixnet.net/blog/post/83880061-%5B%E6%95%99%E5%AD%B8%5Dc-c++-callback-function-%E7%94%A8%E6%B3%95-%E7%AF%84%E4%BE%8B-(%E5%85%A7%E5%90%ABfunctio
https://openhome.cc/Gossip/CppGossip/FunctionPointer.html