yun@dicom

导航

用 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 }
View Code

 

测试代码

 

 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 }
View Code

 

 

总结:

1. 与 C# event 非常类似,易于使用

2. 发送事件前,不用判断是否有监听者 (比C#还方便)

3. 因为把类实例和成员函数转换成了临时的lambda函数,因此没法支持 Pop了.换言之,响应函数只能挂,没法脱离.

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

 

posted on 2020-10-12 12:14  yun@dicom  阅读(601)  评论(0)    收藏  举报