2021年6月13日 星期日

[筆記][C/C++]Class之間使用Callback Function

前言

最近工作需要想辦法優化架構,因此設計了兩個類別各自負責自己工作,但有些資訊是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
  1. std::sort
  2. openCV MouseCallback
  3. 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