Callback在C\C++中的實現
Callback是這樣的一類物件(在這裡不能簡單的理解為"回撥函式"了):你註冊一個函式,以及呼叫它時的引數,希望在滿足某個條件時,以這些註冊的函式呼叫這個回撥,完成指定的操作.
很多地方會使用到這個概念.比如,UI程式中,註冊一個函式,當某個滑鼠事件發生的時候自動呼叫;比如,建立一個執行緒,執行緒開始執行時,執行註冊的函式操作.
Callback的出現,本質上是因為很多操作都有非同步化的需要---你不知道它什麼時候會執行,只需要告訴它,在執行的時候,呼叫我告訴你的操作即可.
儘管使用的地方不盡相同,但是從程式的角度上看,做的事情都是差不多的.
要實現一個Callback,最大的難點在於,變化的引數和需要統一的對外介面之間的矛盾.也就是說,回撥函式執行時引數的數量是你無法預知的.而你需要對外提供一個統一的介面,呼叫該介面的不需要關注到註冊進去的到底是什麼,有幾個引數,具體的執行留到回撥真正執行的時候再去處理.
簡單介紹一下目前我所知道的幾種方法,有C++的,也有C的.
1) 使用模板
將不同引數的型別,作為模板的引數.比如:
int main()
{
Test test;
Closure *callback0 = NewCallback(&test, &Test::Run0);
callback0->Run();
delete callback0;
Closure *callback1 = NewCallback(&test, &Test::Run1, 1);
callback1->Run();
delete callback1;
return 0;
}
在這裡,定義了一個虛擬基類Closure,它對外暴露一個介面Run,也就是,使用它的時候只需要使用Closure指標->Run即可以執行註冊的操作.需要注意的是,Closure的建構函式宣告為protected,也就是僅可以被子類呼叫.
接下來,定義的Closure'子類都是模板類,其中的模板都是引數,我分別實現了兩種子類,分別是不帶引數的和帶一個引數的.將回撥函式需要的引數,儲存在具體的子類物件中.
最後,對外構造一個Closure指標時,最好也提供一致的介面,這裡分別為兩種子類實現了NewCallback函式.
剩下的,理解起來應該不難.
這種實現方法,看明白的就知道,其實難點不多.它將回撥函式和傳遞給回撥函式的引數放在了一個類中,當外部呼叫Run介面的時候,再根據內部的實現來具體進行操作.
但是,我本人很不喜歡模板滿天飛的程式碼,所以應該還有些別的方法來實現吧?
2) 不使用模板,將引數和回撥分離,分別對引數和回撥進行抽象
CEGUI是一款開源的遊戲UI專案,早幾年我還在做著3D引擎程式設計師夢的時候,曾經看過一些,對它的一些程式碼還有些印象.
裡面對UI事件的處理,也使用了類似Callback的機制(這種使用場景最開始的時候曾經說過,所以應該不會感到意外).
在CEGUI中,一個事件由一個虛擬基類Event定義,處理事件的時候呼叫的是它的純虛擬函式fireEvent,而這個函式的引數之一是EventArgs--這又是一個虛擬基類.
所以,熟悉物件導向的人,應該可以很快的反應過來了:在Event的子類中實現fireEvent,而不同的函式引數,可以從EventArgs虛擬基類中派生出來.
於是,具體回撥的時候,僅僅需要呼叫 Event類指標->fireEvent(EventArgs類指標)就可以了.
(我在這裡對CEGUI的講解,省略了很多細節,僅僅關注到最關注的點,感興趣的可以自己去看看程式碼)
對比1)和2)兩種解決方法,顯然對我這樣不喜歡模板的人來說,更喜歡2).除了模板的程式碼讀起來比較頭大,以及模板會讓程式碼量增大之外.喜歡2)的原因還在於,C對"類别範本"機制的支援實在是欠缺,至今除了使用巨集之外,似乎找不到很好的辦法能夠實現類C++的模板機制.但是,如果採用2)的繼承介面的方式,C就可以很清楚的實現出來.所以就有了下面C的實現:
3) C的實現.
有了2)的準備,使用C來實現一個類似的功能,應該很容易了,下面貼程式碼,應該很清楚的:
很多地方會使用到這個概念.比如,UI程式中,註冊一個函式,當某個滑鼠事件發生的時候自動呼叫;比如,建立一個執行緒,執行緒開始執行時,執行註冊的函式操作.
Callback的出現,本質上是因為很多操作都有非同步化的需要---你不知道它什麼時候會執行,只需要告訴它,在執行的時候,呼叫我告訴你的操作即可.
儘管使用的地方不盡相同,但是從程式的角度上看,做的事情都是差不多的.
要實現一個Callback,最大的難點在於,變化的引數和需要統一的對外介面之間的矛盾.也就是說,回撥函式執行時引數的數量是你無法預知的.而你需要對外提供一個統一的介面,呼叫該介面的不需要關注到註冊進去的到底是什麼,有幾個引數,具體的執行留到回撥真正執行的時候再去處理.
簡單介紹一下目前我所知道的幾種方法,有C++的,也有C的.
1) 使用模板
將不同引數的型別,作為模板的引數.比如:
#include <stdio.h>
class Closure
{
public:
virtual ~Closure(){}
virtual void Run() {}
protected:
Closure(){}
};
template<class T>
class Callback0
: public Closure
{
public:
typedef void (T::*Done)();
public:
Callback0(T *obj, Done run)
: object_(obj)
, run_(run)
{
}
virtual void Run()
{
(object_->*run_)();
}
private:
T *object_;
Done run_;
};
template<class T, class T1>
class Callback1
: public Closure
{
public:
typedef void (T::*Done)(T1);
public:
Callback1(T *obj, Done run, T1 arg)
: object_(obj)
, run_(run)
, arg0_(arg)
{
}
virtual void Run()
{
(object_->*run_)(arg0_);
}
private:
T *object_;
Done run_;
T1 arg0_;
};
template<class T>
Closure* NewCallback(T *obj, void (T::*member)())
{
return new Callback0<T>(obj, member);
}
template<class T, class T1>
Closure* NewCallback(T *obj, void (T::*member)(T1), T1 P)
{
return new Callback1<T, T1>(obj, member, P);
}
class Closure
{
public:
virtual ~Closure(){}
virtual void Run() {}
protected:
Closure(){}
};
template<class T>
class Callback0
: public Closure
{
public:
typedef void (T::*Done)();
public:
Callback0(T *obj, Done run)
: object_(obj)
, run_(run)
{
}
virtual void Run()
{
(object_->*run_)();
}
private:
T *object_;
Done run_;
};
template<class T, class T1>
class Callback1
: public Closure
{
public:
typedef void (T::*Done)(T1);
public:
Callback1(T *obj, Done run, T1 arg)
: object_(obj)
, run_(run)
, arg0_(arg)
{
}
virtual void Run()
{
(object_->*run_)(arg0_);
}
private:
T *object_;
Done run_;
T1 arg0_;
};
template<class T>
Closure* NewCallback(T *obj, void (T::*member)())
{
return new Callback0<T>(obj, member);
}
template<class T, class T1>
Closure* NewCallback(T *obj, void (T::*member)(T1), T1 P)
{
return new Callback1<T, T1>(obj, member, P);
}
class Test
{
public:
void Run0()
{
printf("in Test::Run0\n");
}
void Run1(int i)
{
printf("in Test::Run1\n");
}
};
{
public:
void Run0()
{
printf("in Test::Run0\n");
}
void Run1(int i)
{
printf("in Test::Run1\n");
}
};
int main()
{
Test test;
Closure *callback0 = NewCallback(&test, &Test::Run0);
callback0->Run();
delete callback0;
Closure *callback1 = NewCallback(&test, &Test::Run1, 1);
callback1->Run();
delete callback1;
return 0;
}
在這裡,定義了一個虛擬基類Closure,它對外暴露一個介面Run,也就是,使用它的時候只需要使用Closure指標->Run即可以執行註冊的操作.需要注意的是,Closure的建構函式宣告為protected,也就是僅可以被子類呼叫.
接下來,定義的Closure'子類都是模板類,其中的模板都是引數,我分別實現了兩種子類,分別是不帶引數的和帶一個引數的.將回撥函式需要的引數,儲存在具體的子類物件中.
最後,對外構造一個Closure指標時,最好也提供一致的介面,這裡分別為兩種子類實現了NewCallback函式.
剩下的,理解起來應該不難.
這種實現方法,看明白的就知道,其實難點不多.它將回撥函式和傳遞給回撥函式的引數放在了一個類中,當外部呼叫Run介面的時候,再根據內部的實現來具體進行操作.
但是,我本人很不喜歡模板滿天飛的程式碼,所以應該還有些別的方法來實現吧?
2) 不使用模板,將引數和回撥分離,分別對引數和回撥進行抽象
CEGUI是一款開源的遊戲UI專案,早幾年我還在做著3D引擎程式設計師夢的時候,曾經看過一些,對它的一些程式碼還有些印象.
裡面對UI事件的處理,也使用了類似Callback的機制(這種使用場景最開始的時候曾經說過,所以應該不會感到意外).
在CEGUI中,一個事件由一個虛擬基類Event定義,處理事件的時候呼叫的是它的純虛擬函式fireEvent,而這個函式的引數之一是EventArgs--這又是一個虛擬基類.
所以,熟悉物件導向的人,應該可以很快的反應過來了:在Event的子類中實現fireEvent,而不同的函式引數,可以從EventArgs虛擬基類中派生出來.
於是,具體回撥的時候,僅僅需要呼叫 Event類指標->fireEvent(EventArgs類指標)就可以了.
(我在這裡對CEGUI的講解,省略了很多細節,僅僅關注到最關注的點,感興趣的可以自己去看看程式碼)
對比1)和2)兩種解決方法,顯然對我這樣不喜歡模板的人來說,更喜歡2).除了模板的程式碼讀起來比較頭大,以及模板會讓程式碼量增大之外.喜歡2)的原因還在於,C對"類别範本"機制的支援實在是欠缺,至今除了使用巨集之外,似乎找不到很好的辦法能夠實現類C++的模板機制.但是,如果採用2)的繼承介面的方式,C就可以很清楚的實現出來.所以就有了下面C的實現:
3) C的實現.
有了2)的準備,使用C來實現一個類似的功能,應該很容易了,下面貼程式碼,應該很清楚的:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef struct event
{
void (*fireEvent)(void *arg);
void *arg;
}event_t;
typedef struct event_arg1
{
int value;
}event_arg1_t;
void fireEvent_arg1(void *arg)
{
event_arg1_t *arg1 = (event_arg1_t*)arg;
printf("arg 1 = %d\n", arg1->value);
}
#define NewEvent(event, eventtype, callback) \
do { \
*(event) = (event_t*)malloc(sizeof(event_t)); \
assert(*(event)); \
(*(event))->arg = (eventtype*)malloc(sizeof(char) * sizeof(eventtype)); \
assert((*(event))->arg); \
(*(event))->fireEvent = callback; \
} while (0)
#define DestroyEvent(event) \
do { \
free((*(event))->arg); \
free(*(event)); \
} while(0)
int main()
{
event_t *event;
NewEvent(&event, event_arg1_t, fireEvent_arg1);
((event_arg1_t*)(event->arg))->value = 100;
event->fireEvent(event->arg);
DestroyEvent(&event);
return 0;
}
#include <stdlib.h>
#include <assert.h>
typedef struct event
{
void (*fireEvent)(void *arg);
void *arg;
}event_t;
typedef struct event_arg1
{
int value;
}event_arg1_t;
void fireEvent_arg1(void *arg)
{
event_arg1_t *arg1 = (event_arg1_t*)arg;
printf("arg 1 = %d\n", arg1->value);
}
#define NewEvent(event, eventtype, callback) \
do { \
*(event) = (event_t*)malloc(sizeof(event_t)); \
assert(*(event)); \
(*(event))->arg = (eventtype*)malloc(sizeof(char) * sizeof(eventtype)); \
assert((*(event))->arg); \
(*(event))->fireEvent = callback; \
} while (0)
#define DestroyEvent(event) \
do { \
free((*(event))->arg); \
free(*(event)); \
} while(0)
int main()
{
event_t *event;
NewEvent(&event, event_arg1_t, fireEvent_arg1);
((event_arg1_t*)(event->arg))->value = 100;
event->fireEvent(event->arg);
DestroyEvent(&event);
return 0;
}
相關文章
- binder 一個簡單的c++服務的實現,與callback實現C++
- 在C++中實現Python的切片C++Python
- 在C++中實現“屬性 (Property)” (轉)C++
- 在C++中實現變長陣列 (轉)C++陣列
- C++回撥函式(callback)的使用C++函式
- 小寫轉大寫金額在C++中的實現 (轉)C++
- C++中實現無符號右移C++符號
- LinkBlockedQueue的c++實現BloCC++
- [C++]實現memcpyC++memcpy
- C++ binder 實現C++
- C/C++中的實參和形參C++
- C++ 實現Golang裡的deferC++Golang
- FastASR——PaddleSpeech的C++實現ASTC++
- 邱奇數的C++實現C++
- DES密碼的C++實現密碼C++
- C++ shrink_to_fit()的實現C++
- 原型模式的C++實現原型模式C++
- 命令模式(c++實現)模式C++
- 堆排序(實現c++)排序C++
- 堆排序c++實現排序C++
- extern "C"的用途—在C++程式碼中嵌入C程式碼C++C程式
- ->在c++中是什麼意思C++
- 如何快速又正確地在C++裡實現鎖C++
- C++中實現回撥機制的幾種方式[轉]C++
- C/C++—— C++編譯器是如何實現多型C++編譯多型
- UE4純C++實現遊戲中快捷欄C++遊戲
- HTTPS通訊的C++實現HTTPC++
- LRU Cache 的簡單 C++ 實現C++
- C++ 多型的實現及原理C++多型
- 用c++實現淨現值的計算C++
- DFA在C#中的實現:過濾敏感詞C#
- C++“隱藏實現,開放介面”的實現方案C++
- 享元模式(c++實現)模式C++
- 中介者模式(c++實現)模式C++
- 橋接模式(c++實現)橋接模式C++
- 模板方法模式(c++實現)模式C++
- 狀態模式(c++實現)模式C++
- 字典樹及其C++實現C++