1. 程式人生 > 實用技巧 >用 C++ 模板實現 C# event

用 C++ 模板實現 C# event

用 C++ 模板實現 C# event

前言:

C# 的 event 關鍵字支援觀察者模式,而且是在語法級別支援.C++ 的同學大概都很羨慕.
不用嫉妒恨,我們可以用 template 來實現一個,程式碼也不復雜,很簡單.

設計要求和思路

1. 功能和介面都類似 C# 的 event, 便於使用
2. 用 template,這樣可以封裝任意型別和任意數量的引數
3. 響應函式需要支援:
1) 靜態函式
2) lambda 函式
3) 類的成員函式
4) std::bind

使用範例

我們假設已經有了模板類

template <typename... Args>
class Event
{
...
}

我們希望它支援如下方式的使用,這與 C# event 很類似

 1 //    定義兩個事件源
 2 Event <int, std::string>  OnFileOpened;
 3 Event <std::string, int>  OnFileClosed;
 4 
 5 
 6 //    靜態響應函式
 7 static void _OnFileOpened (int a, std::string b)
 8 {
 9     cout << " in static, a:" << a << " b:" << b << endl;
10 } 11 12 13 // 類響應函式 14 class Test_Class 15 { 16 public: 17 void OnFileOpened (int a, std::string b) 18 { 19 cout << " in class::OnFileOpened, a:" << a << " b:" << b << endl; 20 } 21 22 void OnFileClosed (std::string a, int b) 23 { 24 cout << "
in class::OnFileClosed, a:" << a << " b:" << b << endl; 25 } 26 }; 27 28 29 // 類的一個例項 30 Test_Class t; 31 32 33 // 測試 34 void TestEvent () 35 { 36 OnFileOpened += _OnFileOpened; // 把響應函式掛到事件源上 37 38 OnFileOpened (1, "hello"); // 傳送事件 39 40 OnFileClosed ("good morning", 2); // 傳送另一個事件, 注意可以不判斷是否為空 41 42 // 把 lambda 響應函式也掛到事件源上 43 OnFileOpened += [](auto a, auto b) 44 { 45 static int xx = 0; 46 xx ++; 47 cout << " in lambda, a:" << a << " b:" << b << endl; 48 }; 49 50 OnFileOpened (2, "hello, file has opened"); // 傳送事件 51 // OnFileClosed (2, "hello, file has opened"); // 故意傳送一個引數不匹配的事件 52 53 54 // 把類例項+成員函式也掛到事件源上 55 OnFileOpened.Push (&t, &Test_Class::OnFileOpened); 56 OnFileClosed.Push (&t, &Test_Class::OnFileClosed); 57 58 // 傳送事件 59 OnFileOpened (3, "hello, file has opened"); 60 OnFileClosed ("good morning, file has closed", 3); 61 }

完整的程式碼和測試程式碼

完整的程式碼和測試程式碼如下

特別宣告一下里面的一個重要技巧: 把類例項和成員函式轉換成一個臨時的 lambda 函式,也算是另外一種 bind 吧

TmplEvent.tlh

  1 #pragma once
  2 
  3 #include <functional>
  4 
  5 namespace CXXHelper
  6 {
  7 
  8     template <typename... Args>
  9     class Event
 10     {
 11     protected:
 12         std::list <std::function <void (Args...)> > m_handlers;
 13 
 14     public:
 15         Event () { }
 16         Event (Event && from)
 17         {
 18             m_handlers.swap (from.m_handlers);
 19         }
 20 
 21         Event (const Event &) = delete;
 22         Event & operator = (const Event & from) = delete;
 23 
 24         Event & operator = (Event && from)
 25         {
 26             m_handlers.swap (from.m_handlers);
 27             return (*this);
 28         }
 29 
 30         void swap (Event & from)
 31         {
 32             m_handlers.swap (from.m_handlers);
 33         }
 34 
 35         void CopyTo (Event & to) const
 36         {
 37             to.m_handlers = m_handlers;
 38         }
 39 
 40         void Release ()
 41         {
 42             m_handlers.clear ();
 43         }
 44 
 45         virtual ~Event () { }
 46 
 47     public:
 48         inline void operator () (Args... arg)
 49         {
 50             Invoke (arg...);
 51         }
 52 
 53         inline void Invoke (Args... arg)
 54         {
 55             for (auto & h : m_handlers)
 56                 h (arg...);
 57         }
 58 
 59 
 60     public:
 61         inline void RemoveAll ()
 62         {
 63             Release ();
 64         }
 65 
 66         inline bool IsEmpty () const
 67         {
 68             return m_handlers.empty ();
 69         }
 70 
 71         inline int  GetSize () const
 72         {
 73             return (int)m_handlers.size ();
 74         }
 75 
 76         inline void AppendFrom (const Event & from)
 77         {
 78             for (auto h : from.m_handlers)
 79                 this->m_handlers.push_back (h);
 80         }
 81 
 82     public:
 83         template <typename TA>
 84         Event & operator += (TA v)
 85         {
 86             Push (v);
 87             return (*this);
 88         }
 89 
 90     public:
 91         //    lambda 表示式會到這裡
 92         //    Binder (bind 結果) 會到這裡
 93         template <typename TX>
 94         //    inline void Push (const TX & handler)
 95         inline void Push (TX handler)
 96         {
 97             m_handlers.push_back (std::move (handler));
 98         }
 99 
100         //    靜態函式會到這裡
101         inline void Push (void (*fn) (Args...))
102         {
103             m_handlers.push_back (std::move (fn));
104         }
105 
106 
107         //    類的成員函式會到這裡 - 3 個
108         //    這裡用了個小技巧: 把類例項和成員函式轉換成一個臨時的 lambda 函式
109         template <typename TC>
110         inline void Push (TC * inst, void (TC::* mfn) (Args...))
111         {
112             m_handlers.push_back ([inst, mfn] (Args... args) {(*inst.*mfn) (args...); });
113         }
114 
115         template <typename TC>
116         void Push (TC * inst, void (TC::* mfn) (Args...) const)
117         {
118             m_handlers.push_back ([inst, mfn] (Args... args) {(*inst.*mfn) (args...); });
119         }
120 
121         template<typename TC>
122         void Push (const TC * inst, void (TC::* mfn) (Args...) const)
123         {
124             m_handlers.push_back ([inst, mfn] (Args... args) {(*inst.*mfn) (args...); });
125         }
126     };
127 
128 }
View Code

測試程式碼

 1 #include <string>
 2 #include <iostream>
 3 using namespace std;
 4 
 5 #include "TmplEvent.tlh"
 6 using namespace CXXHelper;
 7 
 8 
 9 Event <int, std::string>  OnFileOpened;
10 Event <std::string, int>  OnFileClosed;
11 
12 
13 static void _OnFileOpened (int a, std::string b)
14 {
15     cout << " in static, a:" << a << " b:" << b << endl;
16 }
17 
18 
19 class Test_Class
20 {
21 public:
22     void OnFileOpened (int a, std::string b)
23     {
24         cout << " in class::OnFileOpened, a:" << a << " b:" << b << endl;
25     }
26 
27     void OnFileClosed (std::string a, int b)
28     {
29         cout << " in class::OnFileClosed, a:" << a << " b:" << b << endl;
30     }
31 };
32 
33 
34 Test_Class t;
35 
36 
37 void TestEvent ()
38 {
39     OnFileOpened += _OnFileOpened;
40 
41     OnFileOpened (1, "hello");
42     OnFileClosed ("good morning", 2);
43 
44     OnFileOpened += [](auto a, auto b)
45     {
46         static int xx = 0;
47         xx ++;
48         cout << " in lambda, a:" << a << " b:" << b << endl;
49     };
50 
51     OnFileOpened (2, "hello, file has opened");
52 //    OnFileClosed (2, "hello, file has opened");
53 
54     OnFileOpened.Push (&t, &Test_Class::OnFileOpened);
55     OnFileClosed.Push (&t, &Test_Class::OnFileClosed);
56 
57     OnFileOpened (3, "hello, file has opened");
58     OnFileClosed ("good morning, file has closed", 3);
59 }
View Code

總結:

1. 與 C# event 非常類似,易於使用

2. 傳送事件前,不用判斷是否有監聽者 (比C#還方便)

3. 因為把類例項和成員函式轉換成了臨時的lambda函式,因此沒法支援 Pop了.換言之,響應函式只能掛,沒法脫離.

4. 如果希望支援Pop,需要其他技巧