我們會經常碰到需要使用回調函數的場合,比如:異步socket、定時器、windows消息處理等等。
這里將列出幾種回調函數的實現機制,分析各自的優劣以供選擇。
將 靜態函數 或 靜態成員函數 作為回調函數的實現比較簡單,而且除了像 std::sort 這種地方,一般很少會用到,這里就不多做說明了。下面列出的都是將 成員函數 作為回調函數的實現。
接口類
class CallbackInterface
{
public:
virtual void onCallback() = 0;
...
};
class Callee : public CallbackInterface
{
public:
virtual void onCallback() { ... }
...
};
class Caller
{
public:
void register(CallbackInterface* callback) { ... }
void update()
{
...
callback->onCallback();
...
}
...
};
// main
caller.register(&callee);
這種方式適用於 Callee 和 CallbackInterface 自然符合繼承語義的情況:Callee 是一種 CallbackInterface,而 Caller 作為管理者,面對的是一堆的 CallbackInterface,至於具體是哪個 Callee 在做事,又是怎么做的, Caller 並不需要知道。
如果 Callee 和 CallbackInterface 並不自然符合繼承語義,最好不要使用這種方式,不然可能會碰到下列限制:
- 需要定義 N 個 CallbackInterface,每個 CallbackInterface 需要定義具體接口函數。
- 一個 Callee 可能需要繼承多個 CallbackInterface,且各個 CallbackInterface 的接口函數不能同名。
- 不支持一個 Callee 對應多個 Caller 的情況。
公共基類
class Object {...}
typedef void(Object::*CALLBACK)();
class Callee : public Object
{
public:
void onCallback() { ... }
...
}
class Caller
{
public:
void register(Object* obj, CALLBACK callback) { ... }
void update()
{
...
obj->*callback();
...
}
...
}
// main
caller.register(&callee, (CALLBACK)(&Callee::onCallback));
cocos-2dx 3.0 之前的版本用的就是這種方式。
盡管它已經可以滿足大多數需要回調函數的場合,但也還是有一些顯而易見的缺點:
- 所有的 Callee 需要繼承自一個公共基類 Object。
- register 時需要做強制類型轉換,這使得編譯期無法對回調函數本身的參數類型和數量進行檢查,如果不匹配將導致運行時錯誤。
- 無法在 register 時傳遞不定參數給回調函數。
std::function
class Callee
{
public:
void onCallback() { ... }
...
}
class Caller
{
public:
void register(const std::function<void()>& callback) { ... }
void update()
{
...
callback();
...
}
...
}
// main
caller.register(std::bind(&Callee::onCallback, callee));
對比之前的實現,這種方式幾乎解決了所有的缺點。
- Callee 不需要繼承公共基類或者回調接口類。
- 多個 Callee 可以綁定於多個 Caller。
- register 時不需要進行強制類型轉換,也能在編譯期檢查回調函數類型。
- register 時還能傳遞不定參數給回調函數。
模板
class Callee
{
public:
void onCallback() { ... }
...
}
class Caller
{
public:
void register(const CBFunctor0 & callback) { ... }
void update()
{
...
callback();
...
}
...
}
// main
caller.register(makeFunctor((CBFunctor0*)0,callee,&Callee::onCallback));
需要包含一個回調函數庫:http://www.tedfelix.com/software/callback.h
具體的實現原理和過程可以查看: http://www.tutok.sk/fastgl/callback.html
從使用者角度看,除了不能傳遞不定參數給回調函數,它跟 std::function 方式 幾乎一樣。
當所用編譯器不支持 c++11 特性時,可以考慮用這種方式。
肯定還有其它實現回調機制的方式,碰到的時候再加進來分析。
目前看來 std::function 方式 是一種比較完美的方案,但在實際應用中使用它也還是會碰到一些問題。
而且使用成員函數作為回調函數,還需要考慮當回調函數將被調用時,如何判斷綁定的對象是否已經被銷毀。
等等這些問題,后續將會有專門的篇幅進行探討。