回調函數設計方法

引入:
 
    你顯示器不亮了,你不知道怎麼弄,那你就問在外地幹IT的大表哥,你大表哥告訴你修理的方法,而後須要你本身來操做。
    你大表哥知道怎麼弄,可是本身不去弄,而是由你去弄。
換句話說,你大表哥實現了修理你顯示器的方法,但他不會本身去調用,而是由你去調用。那麼你大表哥告訴你的修機器的方法就是回調函數。
    在這個比喻裏,你本身 做爲主調方,有實際的需求——修顯示器,可是沒有方法,求教表哥的時候,表哥給你的方法 就是一個 函數地址,當你按照大表哥的方法執行的時候,就是 執行了一個回調函數了。
 
  在工程設計中,尤爲在底層庫設計的時候,不少時候,庫的開發者並不能預測從此使 用這段代碼的程序員須要這個函數作具體什麼工做,這時候,就須要使用回調設計。
    C 和 C++都提供這類回調支持,C 的建議是使用函數指針,回調實現,C++則經過對基類的繼承,對基類中虛函數的再設計來實現。不過,根據筆者經驗,在這點上,C 的方式比C++方式要輕靈,而且更加靈活。所以,在筆者的工程開發中,通常使用回調函數設計,不太使用虛函數機制。
    回調函數其實就是函數指針的應用,在 C 中,一切數據都可以指針化表示,函數自己 其實也能夠,當咱們以正確的構型調用一個函數指針時,其效果和直接調用函數自己,完全同樣。
    另外,因爲現代操做系統的 C 親密性,不少操做系統級的 api 設計均可以看到回調函數 ,好比咱們常見的線程函數,甚至進程自己,其實都是操做系統的回調函數,beginthread 這類啓動線程的調用,通常就是把指定的線程函數指針,在系統的線程表中,註冊一個新的表項,系統下一輪時間循環,自動會根據這個表項,回調該指針,進而實現應用程序線程對時間片的獲取。而且,這個過程,通常都是純 C 的,和 C++無關。
    從某種意義上說,現代並行計算,是創建在 C 的回調模型上的。做爲程序員,對於回調函數,應該有很深刻的認識,並能熟練應用。
    回調函數的設計很是簡單,不過,這裏面首先要搞清楚兩個身份,一個是回調函數的
設計者,一個是使用者,但兩者都是程序員。
    在後文中,使用 回調模型設計者和使用者來區分這兩個身份。
 

回調模型設計者:

    做爲回調模型的設計者,首先須要定義一個回調函數構型,由於 C 語言就算再靈活, 也須要知道函數原型是怎樣的,才能確保使用者是正確調用,避免崩潰。
typedef void(*_APP_INFO_OUT_CALLBACK)(char* szInfo,void* pCallParam);

 

一、typedef,這是咱們顯式定義一種新的變量類型,這個變量類型,就是這一個回調函 數指針的類型。之後使用這個指針的設計者和使用者,均可以使用 _APP_INFO_OUT_CALLBACK 這個變量類型來定義本身的指針變量。
二、本回調函數使用 void 做爲返回值,是由於這個特殊應用。其實不少時候,有個約定 ,通常回調函數使用 bool 做爲返回值,這在某些循環遍歷的場合,當使用者感到本身的數據已經找到,循環無需繼續,能夠返回個 false,設計者就知道,能夠再也不循環了。這體現出使用者不是徹底被動的接受回調,也能夠經過返回值影響回調發起方的邏輯。
三、char* szInfo 這是業務數據,這裏再也不細說。
四、void* pCallParam,這個很是關鍵,全部回調函數的設計者,必定要幫助使用者傳遞 一根 void*的指針,並透傳到每一次回調調用中。
例子:
建立一個支持回調的 類
classCStultzLowDebug
{
public:
CStultzLowDebug(char* szPathName,
char* szAppName,
//構造函數傳入回調函數和參數,能夠是 null
_APP_INFO_OUT_CALLBACK pInfoOutCallback=null,
void* pInfoOutCallbackParam=null);
//保存在對象內部,方便 Debug 等功能函數調用
_APP_INFO_OUT_CALLBACK m_pInfoOutCallback;
void* m_pInfoOutCallbackParam;
};

 

構造函數 的具體實現:
CStultzLowDebug::CStultzLowDebug(char* szPathName,
char* szAppName,
_APP_INFO_OUT_CALLBACK pInfoOutCallback,
void* pInfoOutCallbackParam)
{
  m_pInfoOutCallback=pInfoOutCallback;//回調函數指針保存
  m_pInfoOutCallbackParam=pInfoOutCallbackParam;//參數指針保存
//
}

 

設計者 對回調函數 的調用方式
intCStultzLowDebug::Debug2File(char*szFormat,...)
{
//
if(m_pInfoOutCallback)//標準寫法,先判斷指針有效性
{
  m_pInfoOutCallback(szInfoOut,//像函數同樣調用
  m_pInfoOutCallbackParam);//這裏在幫助透傳指針
}
//
}

 

 
總結 回調函數的設計的特色:
一、先定義回調函數原型,順便定義一個新的指針變量類型。
二、設計者以該回調函數指針變量類型定義新的變量,實現參數傳遞和數據保存。
三、調用前先檢查指針有效性,避免跳到空指針處,形成崩潰。
 
 

回調模型使用者

 
    做爲使用者來講,若是回調函數設計者均基於上述方法設計,其調用程序設計也能夠
造成簡單規律和套路。
使用者首先必須以回調函數構型構建一個函數,這就是未來的回調函數實體,設計者
的模塊會跳至此處運行。使用者在這個函數內部,直接使用傳來的變量 szInfo 便可,這就是每次 Debug 模塊輸出的字符串。
void ApplicationInfomationOutCallback(char* szInfo,void* pCallParam);

 

    但有一點注意,若是是 C 裏面,能夠這樣直接聲明和實現函數便可。但在 C++的類中 ,不能這樣直接寫。這是因爲 C++的編譯器,爲每個類成員函數,提供了一個默認的隱含指針 this做爲參數,指向本次實例化的對象,其類型就是這個類自己。所以,以下所述,這個函數就不對了。
classCStultzLowDebug
{
private:
    voidApplicationInfomationOutCallback(char* szInfo,void* pCallParam);
};
此時的回調函數原型,因爲是類成員函數,有隱含指針,所以至關於以下原型
voidApplicationInfomationOutCallback(
CStultzLowDebug*this,//這是 C++編譯器在編譯時強行添加的
char* szInfo,
void* pCallParam);

 

這時,咱們再和回調函數原型比較,發現多了一個 this 指針。 兩個函數不是一個構型,函數指針類型不匹配,調用將會失敗。
所以,全部的回調函數,一旦寫在類裏面,必須用 static 修飾爲靜態成員函數。
classCStultzLowDebug
{
private:
//請注意這裏的 static 修飾
    static void ApplicationInfomationOutCallback(char* szInfo,void* pCallParam);
};

 

    C++規定,對於靜態類成員函數,將不提供隱含的 this 指針,所以,函數的編譯後本體和書寫時的聲明徹底同樣,這樣就能夠把這個函數做爲回調函數。
    但這隨之帶來另一個問題,就是沒有了 this 指針,使用起來很不方便。咱們知道 , C++的面向對象設計中,其對象的核心定義就是「一批數據和針對該數據的全部方法的集 合。這是面向對象程序設計的精髓。
 一個類的成員函數方法,通常說來,都和這個類實例化的對象所包含的數據密
切相關,程序中須要不斷訪問本對象的成員變量或其餘成員函數,也就須要頻繁訪問本對象指針 this。   
所以 在C++裏,咱們能夠有以下的解決方案,即 傳參。而 回調函數設計者有義務爲使用者透傳一根  void* 的參數指針
 
   在 實際使用時,將this指針 做爲 參數,傳給 回調函數,那麼 就能夠直接使用this ,從而操做 類中的數據了。
 
另外,若是 有 多個參數 須要 傳遞,就須要使用 傳入結構體 指針的方式。
 
 
 
參考文獻:《0 bug C/C++商用工程之道》
相關文章
相關標籤/搜索