C++回调机制的几种实现方式
Callback
Callback的本質是設置一個函數指針進去,然后在需要觸發某個事件時調用該方法, 比如Windows的窗口消息處理函數就是這種類型。
比如下面的示例代碼,我們在Download完成時需要觸發一個通知外面的事件:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
Sink
Sink的本質是你按照對方要求實現一個C++接口,然后把你實現的接口設置給對方,對方需要觸發事件時調用該接口, COM中連接點就是居于這種方式。還是以上面的Download為例(你調用對方的下載類實現下載功能):
/*對方要求的接口*/ class IDownloadSink { public:virtual void OnDownloadFinished(const char* pURL, bool bOK) = 0; }; /*對方的實現*/ class CMyDownloader { public:CMyDownloader(IDownloadSink* pSink) : m_pSink(pSink) { }void DownloadFile(const char* pURL){……if(m_pSink != NULL)m_pSink->OnDownloadFinished(pURL, true);}private:IDownloadSink* m_pSink; };- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 15
Delegate
Delegate的本質是設置成員函數指針給對方,然后讓對方在需要觸發事件時調用。C#中用Delegate的方式實現Event,讓C++程序員很是羨慕,C++中因為語言本身的關系,要實現Delegate還是很麻煩的。上面的例子我們用Delegate的方式實現如下:
class CDownloadDelegateBase { public:virtual void Fire(const char* pURL, bool bOK) = 0; };/*模板類,實現代理函數的調用*/ template<typename O, typename T> class CDownloadDelegate: public CDownloadDelegateBase {typedef void (T::*Fun)(const char*, bool); public:CDownloadDelegate(O* pObj = NULL, Fun pFun = NULL):m_pFun(pFun), m_pObj(pObj){ } virtual void Fire(const char* pURL, bool bOK){if(m_pFun != NULL && m_pObj != NULL){(m_pObj->*m_pFun)(pURL, bOK);}} private:Fun m_pFun;O* m_pObj; };/*模板函數,創建代理*/ template<typename O, typename T> CDownloadDelegate<O,T>* MakeDelegate(O* pObject, void (T::*pFun)(const char* pURL, bool)) {return new CDownloadDelegate<O, T>(pObject, pFun); }/*代理函數管理*/ typedef vector<CDownloadDelegateBase*> CDownloadDelegates; class CDownloadEvent { public:~CDownloadEvent(){CDownloadDelegates::iterator it = m_arDelegates.begin();while (it != m_arDelegates.end()){delete *it;++it;}m_arDelegates.clear();}void operator += (CDownloadDelegateBase* p){m_arDelegates.push_back(p);}void operator -= (CDownloadDelegateBase* p){CDownloadDelegates::iterator it = remove(m_arDelegates.begin(), m_arDelegates.end(), p);while (it != m_arDelegates.end()){delete *it;++it;}m_arDelegates.erase(it, m_arDelegates.end());}void operator()(const char* pURL, bool bOK){CDownloadDelegates::iterator it = m_arDelegates.begin();while (it != m_arDelegates.end()){(*it)->Fire(pURL, bOK);++it;}} private:CDownloadDelegates m_arDelegates; };class CMyDownloaderEx { public:void DownloadFile(const char* pURL){……downloadEvent(pURL, true);}CDownloadEvent downloadEvent; };/*應用*/ class CMyFileEx { public:void download(){CMyDownloaderEx downloader;/*添加代理函數*/downloader.downloadEvent += MakeDelegate(this, &CMyFileEx::OnDownloadFinished);downloader.DownloadFile("www.baidu.com");}virtual void OnDownloadFinished(const char* pURL, bool bOK){……} };- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
可以看到Delegate的方式代碼量比上面其他2種方式多多了,并且上面是固定參數數量和類型的實現方式,如果要實現可變參數,要更加麻煩的多,具體可參考:
http://www.codeproject.com/Articles/11464/Yet-Another-C-style-Delegate-Class-in-Standard-C
http://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible
std::function(since C++ 11, vs2010)
template< class R, class... Args > class function<R(Args...)>- 1
- 2
- 1
- 2
類模板std :: function是一個通用的多態函數包裝器。std :: function的實例可以存儲,復制和調用任何可調用的目標:函數、lambda表達式、綁定表達式或其他函數對象。
#include <functional> #include <iostream>struct Foo {Foo(int num) : num_(num) {}void print_add(int i) const { std::cout << num_+i << '\n'; }int num_; };void print_num(int i) {std::cout << i << '\n'; }int main() {// store a free functionstd::function<void(int)> f1 = print_num;f1(-9);// store a lambdastd::function<void()> f2 = []() { print_num(123); };f2();// store the result of a call to std::bindstd::function<void()> f3 = std::bind(print_num, 12345);f3();// store a call to a member functionstd::function<void(const Foo&, int)> f4 = &Foo::print_add;Foo foo(13579);f4(foo, 1); }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
std::mem_fn (since C++ 11, vs2010)
template< class R, class T > /*unspecified*/ mem_fn(R T::* pm); template< class R, class T, class... Args > /*unspecified*/ mem_fn(R (T::* pm)(Args...)); template< class R, class T, class... Args > /*unspecified*/ mem_fn(R (T::* pm)(Args...) const); template< class R, class T, class... Args > /*unspecified*/ mem_fn(R (T::* pm)(Args...) volatile); template< class R, class T, class... Args > /*unspecified*/ mem_fn(R (T::* pm)(Args...) const volatile); template< class R, class T, class... Args > /*unspecified*/ mem_fn(R (T::* pm)(Args...) &); template< class R, class T, class... Args > /*unspecified*/ mem_fn(R (T::* pm)(Args...) const &); template< class R, class T, class... Args > /*unspecified*/ mem_fn(R (T::* pm)(Args...) volatile &); template< class R, class T, class... Args > /*unspecified*/ mem_fn(R (T::* pm)(Args...) const volatile &); template< class R, class T, class... Args > /*unspecified*/ mem_fn(R (T::* pm)(Args...) &&); template< class R, class T, class... Args > /*unspecified*/ mem_fn(R (T::* pm)(Args...) const &&); template< class R, class T, class... Args > /*unspecified*/ mem_fn(R (T::* pm)(Args...) volatile &&); template< class R, class T, class... Args > /*unspecified*/ mem_fn(R (T::* pm)(Args...) const volatile &&);- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
函數模板std :: mem_fn生成指向成員函數的指針的包裝對象,它可以存儲,復制和調用指向成員函數的指針。 在調用std :: mem_fn時,可以使用對象的引用和指針(包括智能指針)。
/* Use mem_fn to store and execute a member function:*/ #include <functional> #include <iostream> struct Foo { void display_greeting() { std::cout << "Hello, world.\n"; } void display_number(int i) { std::cout << "number: " << i << '\n'; } }; int main() {Foo foo;auto func_greet = std::mem_fn(&Foo::display_greeting);func_greet(foo); auto func_display = std::mem_fn(&Foo::display_number);func_display(foo, 42); }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
小結
1) Callback方法是面向過程的,使用簡單而且靈活,正如C語言本身;
2) Sink方法是面向對象的,在C++里使用較多, 可以在一個Sink里封裝一組回調接口,適用于一系列比較固定的回調事件;
3) Delegate方法也是面向對象的,和Sink封裝一組接口不同,Delegate的封裝是以函數為單位,粒度比Sink更小更靈活;
4) std::function和std::bind組合使用也可以實現類似函數指針的功能,但卻卻比函數指針更加靈活,特別是函數指向類的非靜態成員函數時(本質上講全局函數和靜態成員函數沒有區別,使用方法上除了靜態成員函數在引用時要在前面加域作用符classname::外,沒有其它任何區別;事實上全局函數也有可能放入命名空間或者使用全局域作用符,例如 namespace::function() 或::function,這樣不僅本質上相同,形勢上也與靜態成員函數一致了)。在Effective C++中ITEM35建議使用該方法實現Strategy模式,實際上也可以用于代替callback 函數模仿C#中的event對象,而這里只能實現一個函數,實際上如果function<>模板實現了operator+以后,再在 function對象中維護一個列表,便可以實現C#中的event特性,即Delegate。
總結
以上是生活随笔為你收集整理的C++回调机制的几种实现方式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 程序员的本质
- 下一篇: C/C++函数调用的几种方式总结