原文章地址:http://www.cnblogs.com/zplutor/archive/2011/09/17/2179756.html
【委託模式 C++實現】
我對.Net的委託模型印象很深刻,使用委託,可以快速實現觀察者模式,免去寫很多繁雜重複的代碼。遺憾的是,C++並沒有提供這樣的模型,爲了達到相似的目的,需要繼承一個類並重寫virtual方法,這種做法需要寫很多代碼,效率比較低下(使用過MFC的應該都能體會到)。然而,在強大的C++面前,沒有什麼是不可能的,已經有很多人針對這個問題進行過研究,並且實現了各種委託模型,其中最著名的就是FastDelegate,這個模型在《Member Function Pointers and the Fastest Possible C++ Delegates》中提出(原文地址:http://www.codeproject.com/KB/cpp/FastDelegate.aspx)。這個模型的特點就是“Fast”,因此不可避免地要依賴編譯器的具體實現,雖然文章的最後說明該模型已在大部分的編譯器上通過了測試,我仍然對此不太放心,要是哪個編譯器升級後改變了實現方式,這個模型就不適合使用了。而且,由於自身水平有限以及懶惰的心理,我也不想去深究每種編譯器的具體實現方式。我想要的是符合C++標準,與編譯器無關的模型,而不管它是否“Fast”。經過不斷的摸索,終於寫出了這樣的一個委託模型,下面與大家分享一下該模型的實現原理。(當然,如果你認爲FastDelegate已經滿足需求,而且不擔心它依賴於編譯器,那麼完全可以忽略本文)
成員函數指針的操作
在開始之前首先介紹一下成員函數指針,它與非成員函數指針的操作方式有很大的不同。有這麼一個類:
1
2
3
4
|
class A
{ public : void Func( int )
{ … } }; |
要取得Func函數的指針,必須這麼做:
1
|
void (A::*pFunc)( int )
= &A::Func; |
::*是一個特殊的操作符,表示pFunc是一個指針,指向A的成員函數。獲取成員函數的地址不能通過類對象來獲取,必須像上面的那樣,通過類名獲取,而且要加上取地址操作符(&)。
那麼如何通過成員函數指針來調用該函數呢?成員函數都有一個隱含的this參數,表示函數要操作的對象,現在我們只獲取到了函數的指針,還缺少一個對象作爲this參數。爲了達到這個目的,需要先創建一個對象,然後通過該對象來調用成員函數指針:
1
2
3
4
5
|
A
a; (a.*pFunc)(10); A*
pa = &a; (pa->*pFunc)(11); |
第一種方式是通過對象本身來調用,第二種方式是通過對象指針來調用,兩種方式的效果都是一樣的。.*和->*都是特殊的操作符,不必糾結於它們奇怪的樣子,只要知道它們只用於調用成員函數指針就行了。
第一步:使用類模板
通過上面的介紹,我們知道了要調用一個成員函數,僅僅有成員函數指針是不夠的,還需要一個對象指針,所以要用一個類將兩者綁到一起。由於對象的類型是無窮多的,所以這裏必須使用類模板:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
template < typename T> class DelegateHandler
{ public : DelegateHandler(T*
pT, void (T::*pFunc)( int )) :
m_pT(pT), m_pFunc(pFunc) { } void Invoke( int value)
{ (m_pT->*m_pFunc)(value); } private : T*
m_pT; void (T::*m_pFunc)( int ); }; |
可以像下面那樣使用該模板:
1
2
3
4
5
6
7
|
A
a; DelegateHandler<A>
ah(&a, &A::Func); ah.Invoke(3); B
b; DelegateHandler<B>
bh(&b, &B::Method); //B::Method的聲明與A::Func一致 bh.Invoke(4); |
到這裏產生了一個問題:如果希望調用的目標是非成員函數,怎麼辦?上面的類模板無法調用非成員函數,不過使用模板偏特化就可以解決這個問題:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
template <> class DelegateHandler< void >
{ public : DelegateHandler( void (*pFunc)( int ))
:
m_pFunc(pFunc) { } void Invoke( int value)
{ (*m_pFunc)(value); } private : void (*m_pFunc)( int ); }; |
使用方法也是一樣的:
1
2
|
DelegateHandler< void >
h(NonmemberFunc); //
void NonmemberFunc(int); h.Invoke(5); |
也許你會有疑問:非成員函數不需要將函數指針和對象指針綁到一起,爲什麼這裏還要用一個類來包裝函數指針?看了下面的內容自然會明白了。
第二步:使用多態
對於單目標的委託來說,使用上面的代碼或許就已經足夠了。但是我的目的當然不止於此,我想要的是多目標的委託。多目標委託其實就是一個容器,在這個容器裏可以存放多個對象,當調用委託的時候依次調用每個對象。容器裏的對象應該都是相同的類型,這樣才能夠放到強類型的容器中;而且委託調用方不應該知道具體的調用目標是什麼,所以這些對象也應該要隱藏具體的細節。遺憾的是,上一步中實現的類模板都不具備這些能力,DelegateHandler<A>和DelegateHandler<B>是不同的類型,不能放到同一個容器中,調用方要調用它們也必須知道調用的目標是什麼類型。
解決這個問題的方法就是使用多態,令所有的委託目標類都繼承一個公共的接口,調用方只通過這個接口來進行調用,這樣就不必知道每個目標具體的類型。下面就是該接口的定義:
1
2
3
4
5
6
|
class IDelegateHandler
{ public : virtual ~IDelegateHandler()
{ } virtual void Invoke( int )
= 0; }; |
然後令DelegateHandler繼承該接口:
1
2
3
4
5
6
7
8
9
|
template < typename T> class DelegateHandler
: public IDelegateHandler
{ … } template <> class DelegateHandler< void >
: public IdelegateHandler
{ … } |
現在可以將各種類型的DelegateHandler放到同一個容器中,並使用同樣的方式來調用了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
A
a; B
b; DelegateHandler<A>
ah(&a, &A::Func); DelegateHandler<B>
bh(&b, &B::Method); DelegateHandler< void >
vh(NonmemberFunc); std::vector<IDelegateHandler*>
handlers; handlers.push_back(&ah); handlers.push_back(&bh); handlers.push_back(&vh); for ( auto it
= handlers.cbegin(); it != handlers.cend(); ++it) { (*it)->Invoke(7); } |
第三步:使用宏
不知道你注意到沒有,上面寫了那麼多代碼,只是爲了實現一個返回值爲void,有一個int參數的委託!如果要實現更多類型的委託,上面的代碼就要重複很多次了。幸好,C++有宏這個東西,使用它可以幫助我們快速生成大量代碼。然而這個宏的定義可不是那麼簡單,爲了它我費了好大週摺。下面開始講述這個探索的過程,如果不想看我囉嗦,可以直接跳到後面看現成的代碼。
我們都知道,函數參數的聲明可以只有類型而沒有名稱,但是爲了在函數內使用參數,該參數必須有名稱。例如:
1
2
3
4
5
6
|
void Invoke( int )
{ //不能使用參數 } void Invoke( int value)
{ //可以通過value這個名稱來使用參數 } |
另外,調用函數的時候只能使用名稱,不能帶有類型:
1
2
|
int value
= 10; Invoke(value); |
這些問題似乎都顯而易見,根本不值一提,但這些就是定義宏的關鍵。一開始我想象宏的使用應該是這樣的:
1
|
DELEGATE( void ,
DelegateHandler, int ,
int ); |
毫無疑問,在它的定義中,從第三個參數開始應該使用可變參數,像這樣(只截取了定義的一部分):
1
2
3
4
5
6
|
#define
DELEGATE(retType, name, …) \ … retType
Invoke(__VA_ARGS__) { \ return (*m_pFunc)(__VA_ARGS__);
\ }
\ … |
展開後的代碼是這樣的:
1
2
3
4
5
|
… void Invoke( int ,
int )
{ return (*m_pFunc)( int ,
int ); } … |
這樣很明顯是錯誤的,即使在定義委託的時候加上參數名稱也不行。問題的原因是函數參數的聲明方式與調用方式不同,而且我們不能將__VA_ARGS__拆開來處理,我們沒辦法爲參數添加名稱,也不能去掉參數的名稱。
既然如此,我們就使用兩個__VA_ARGS__,一個用於函數參數的聲明,一個用於調用。以上面的爲例,第一個__VA_ARGS__應該是這樣子:
1
|
int a,
int b |
第二個__VA_ARGS__應該是這樣子:
1
|
a,
b |
宏展開之後應該是這樣子:
1
2
3
4
5
|
… void Invoke( int a,
int b)
{ return (*m_pFunc)(a,
b); } … |
這樣就正確了。可是這樣又帶來了一個新問題:一個宏裏只能使用一個可變參數。解決方法是,使用另外的宏來產生這兩個__VA_ARGS__!好了,我不再說廢話了,直接給出代碼來,代碼比我的表達能力更強。
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
|
#define
DECLARE_PARAMS(...) __VA_ARGS__ #define
DECLARE_ARGS(...) __VA_ARGS__ //0個參數的委託 #define
DELEGATE0(retType, name) \ DECLARE_DELEGATE(retType,
name, DECLARE_PARAMS( void ),
) //1個參數的委託 #define
DELEGATE1(retType, name, p1) \ DECLARE_DELEGATE(
\ retType,
\ name,
\ DECLARE_PARAMS(p1
a), \ DECLARE_ARGS(a)) //2個參數的委託 #define
DELEGATE2(retType, name, p1, p2) \ DECLARE_DELEGATE(
\ retType,
\ name,
\ DECLARE_PARAMS(p1
a, p2 b), \ DECLARE_ARGS(a,
b)) //3個參數的委託 #define
DELEGATE3(retType, name, p1, p2, p3) \ DECLARE_DELEGATE(
\ retType,
\ name,
\ DECLARE_PARAMS(p1
a, p2 b, p3 c), \ DECLARE_ARGS(a,
b, c)) //4個參數的委託 #define
DELEGATE4(retType, name, p1, p2, p3, p4) \ DECLARE_DELEGATE(
\ retType,
\ name,
\ DECLARE_PARAMS(p1
a, p2 b, p3 c, p4 d), \ DECLARE_ARGS(a,
b, c, d)) //5個參數的委託 #define
DELEGATE5(retType, name, p1, p2, p3, p4, p5) \ DECLARE_DELEGATE(
\ retType,
\ name,
\ DECLARE_PARAMS(p1
a, p2 b, p3 c, p4 d, p5 e), \ DECLARE_ARGS(a,
b, c, d, e)) //6個參數的委託 #define
DELEGATE6(retType, name, p1, p2, p3, p4, p5, p6) \ DECLARE_DELEGATE(
\ retType,
\ name,
\ DECLARE_PARAMS(p1
a, p2 b, p3 c, p4 d, p5 e, p6 f), \ DECLARE_ARGS(a,
b, c, d, e, f)) //7個參數的委託 #define
DELEGATE7(retType, name, p1, p2, p3, p4, p5, p6, p7) \ DECLARE_DELEGATE(
\ retType,
\ name,
\ DECLARE_PARAMS(p1
a, p2 b, p3 c, p4 d, p5 e, p6 f, p7 g), \ DECLARE_ARGS(a,
b, c, d, e, f, g)) //8個參數的委託 #define
DELEGATE8(retType, name, p1, p2, p3, p4, p5, p6, p7, p8) \ DECLARE_DELEGATE(
\ retType,
\ name,
\ DECLARE_PARAMS(p1
a, p2 b, p3 c, p4 d, p5 e, p6 f, p7 g, p8 h), \ DECLARE_ARGS(a,
b, c, d, e, f, g, h)) #define
DECLARE_DELEGATE(retType, name, params, args) \ class I##name
{ \ public :
\ virtual ~I##name()
{ } \ virtual retType
Invoke(params) = 0; \ };
\ template < typename T>
\ class name
: public I##name
{ \ public :
\ name(T*
pType, retType (T::*pFunc)(params)) \ :
m_pType(pType), m_pFunc(pFunc) { } \ retType
Invoke(params) { \ return (m_pType->*m_pFunc)(args);
\ }
\ private :
\ T*
m_pType; retType (T::*m_pFunc)(params); \ };
\ template <>
\ class name< void >
: public I##name
{ \ public :
\ name(retType
(*pFunc)(params)) \ :
m_pFunc(pFunc) { } \ retType
Invoke(params) { \ return (*m_pFunc)(args);
\ }
\ private :
\ retType
(*m_pFunc)(params); \ } |
注意最後面少了一個分號,這是故意爲之的,爲了強迫在定義委託的時候加上分號。這種宏定義的方法對參數個數有了限制,我這裏的定義最多隻支持8個參數,爲了支持更多參數,需要寫更多的代碼。其實我認爲8個參數已經足夠了,超過8個參數的函數不是好的設計,應該重新考慮一下。