用 C++ 模板實現 C# event
阿新 • • 發佈:2020-10-12
用 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,需要其他技巧