用 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 // 把 lambda 响应函数也挂到事件源上 38 OnFileOpened += [](auto a, auto b) 39 { 40 static int xx = 0; 41 xx ++; 42 cout << " in lambda, a:" << a << " b:" << b << endl; 43 }; 44 45 // 把类实例+成员函数也挂到事件源上 46 OnFileOpened.Push (&t, &Test_Class::OnFileOpened); 47 OnFileClosed.Push (&t, &Test_Class::OnFileClosed); 48 49 // 发送事件 50 OnFileOpened (3, "hello, file has opened"); 51 OnFileClosed ("good morning, file has closed", 3); 52 }
完整的代码和测试代码
完整的代码和测试代码如下
特别声明一下里面的一个重要技巧: 把类实例和成员函数转换成一个临时的 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 }
测试代码
1 #include <string> 2 #include <iostream> 3 using namespace std; 4 5 #include "TmplEvent.tlh" 6 using namespace CXXHelper; 7 8 9 // 定义两个带参数的事件源, 故意把参数顺序做的不一样 10 Event <int, std::string> OnFileOpened; 11 Event <std::string, int> OnFileClosed; 12 13 Event <> OnNothing; // 定义一个无参数的事件源 14 15 16 static void _OnNothing () 17 { 18 cout << " Enter OnNothing " << endl; 19 } 20 21 22 static void _OnFileOpened (int a, std::string b) 23 { 24 cout << " in static, a:" << a << " b:" << b << endl; 25 } 26 27 28 class Test_Class 29 { 30 public: 31 void OnFileOpened (int a, std::string b) 32 { 33 cout << " in class::OnFileOpened, a:" << a << " b:" << b << endl; 34 } 35 36 void OnFileClosed (std::string a, int b) 37 { 38 cout << " in class::OnFileClosed, a:" << a << " b:" << b << endl; 39 } 40 }; 41 42 43 Test_Class t; 44 45 46 void TestEvent_1 () 47 { 48 OnFileOpened += _OnFileOpened; // 把响应函数挂到事件源上 49 50 OnFileOpened (1, "hello"); // 发送事件 51 OnFileClosed ("good morning", 2); // 发送另一个事件, 注意可以不判断是否为空 52 53 // 把 lambda 响应函数也挂到事件源上 54 OnFileOpened += [](auto a, auto b) 55 { 56 cout << " in lambda, a:" << a << " b:" << b << endl; 57 }; 58 59 OnFileOpened (2, "hello, file has opened"); // 发送事件 60 // OnFileClosed (2, "hello, file has opened"); // 故意发送一个参数不匹配的事件 61 62 // 把类实例+成员函数也挂到事件源上 63 OnFileOpened.Push (&t, &Test_Class::OnFileOpened); 64 OnFileClosed.Push (&t, &Test_Class::OnFileClosed); 65 66 // 发送事件 67 OnFileOpened (3, "hello, file has opened"); 68 OnFileClosed ("good morning, file has closed", 3); 69 70 OnNothing += _OnNothing; // 把响应函数挂到事件源上 71 OnNothing (); 72 }
总结:
1. 与 C# event 非常类似,易于使用
2. 发送事件前,不用判断是否有监听者 (比C#还方便)
3. 因为把类实例和成员函数转换成了临时的lambda函数,因此没法支持 Pop了.换言之,响应函数只能挂,没法脱离.
4. 如果希望支持Pop,需要其他技巧
浙公网安备 33010602011771号