設計模式--委託模式 C++實現

原文章地址: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個參數的函數不是好的設計,應該重新考慮一下。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章