打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
在C++中实现事件(委托)
C++中实现回调机制的几种方式一文中,我们提到了实现回调的三种方式(C风格的回调函数, Sink方式和Delegate方式)。在面向对象开发中,delegate的方式是最灵活和方便的,因此很早就有人用复杂的模板去模拟, 实现起来很复杂。但是现在借助C++11的functionbind, 我们可以很方便的去实现。下面是我自己的一种实现方式:
  1 #pragma once
  2 
  3 #include <functional>
  4 #include <algorithm>
  5 #include <vector>
  6 #include <assert.h>
  7 
  8 namespace Common
  9 {
 10     typedef void* cookie_type;
 11 
 12     template<typename TR, typename T1, typename T2>
 13     class CEvent
 14     {
 15     public:
 16         typedef TR return_type;
 17         typedef T1 first_type;
 18         typedef T2 second_type;
 19 
 20         typedef std::function<return_type (first_type, second_type)> handler_type;
 21 
 22         ~CEvent()
 23         {
 24             Clear();
 25         }
 26 
 27         return_type operator()(first_type p1, second_type p2)
 28         {
 29             return_type ret = return_type();
 30             size_t size = _handlers.size();
 31             for(size_t i=0; i<size; ++i)
 32             {
 33                 ret = _handlers[i]->operator()(p1, p2);
 34             }
 35             return ret;
 36         }
 37 
 38         cookie_type AddHandler(std::function<return_type (first_type, second_type)> h)
 39         {
 40             CEventHandler*p = new(nothrow)  CEventHandler(h);
 41             if(p != nullptr) _handlers.push_back(p);
 42             return (cookie_type)p;
 43         }
 44 
 45         template<typename class_type, typename class_fun>
 46         cookie_type AddHandler(class_type* pThis, class_fun f)
 47         {
 48             CEventHandler* p = new(nothrow) CEventHandler(pThis, f);
 49             if(p != nullptr) _handlers.push_back(p);
 50             return (cookie_type)p;
 51         }
 52 
 53         void RemoveHandler(cookie_type cookie)
 54         {
 55             CEventHandler* p = (CEventHandler*)cookie;
 56 
 57             auto itr = std::find(_handlers.begin(), _handlers.end(), p);
 58             if(itr != _handlers.end())
 59             {
 60                 _handlers.erase(itr);
 61                 delete p;
 62             }
 63             else
 64             {
 65                 assert(false);
 66             }
 67         }
 68 
 69         void Clear()
 70         {
 71             if(!_handlers.empty())
 72             {
 73                 int n = _handlers.size();
 74                 std::for_each(_handlers.begin(), _handlers.end(), [](CEventHandler* p)
 75                 { 
 76                     assert(p != nullptr);
 77                     delete p;
 78                 });
 79                 _handlers.clear();        
 80             }
 81         }
 82 
 83     private:
 84         class CEventHandler 
 85         {
 86         public:
 87             CEventHandler(handler_type h)
 88             {
 89                 _handler = h;
 90                 assert(_handler != nullptr);
 91             }
 92 
 93             template<typename class_type, typename class_fun>
 94             CEventHandler(class_type* pThis, class_fun object_function)
 95             {
 96                 using namespace std::placeholders;
 97                 _handler = std::bind(object_function, pThis, _1, _2);
 98                 assert(_handler != nullptr);
 99             }
100 
101             return_type operator()(first_type p1, second_type p2)
102             {
103                 return_type ret = return_type();
104                 assert(_handler != nullptr);
105                 if(_handler != nullptr) ret = _handler(p1, p2);
106                 return ret;
107             }
108 
109             handler_type _handler;
110         };
111 
112 
113     private:
114         std::vector<CEventHandler*> _handlers;
115     };
116 
117 } //Common

大概实现思想是我们通过一个内置的CEventHandler 类来封装处理函数,我们可以通过AddHandler来添加事件处理函数,添加时会返回一个Cookie,我们可以通过该Cookie来RemoveHandler, 下面是测试代码:
 1 // EventTest.cpp : Defines the entry point for the console application.
 2 //
 3 
 4 #include "stdafx.h"
 5 
 6 #include <iostream>
 7 #include "event1.h"
 8 
 9 using namespace std;
10 
11 class CObjectX 
12 {
13 
14 };
15 
16 class CClickEventArgs: public CObjectX
17 {
18 
19 };
20 
21 
22 class CButton: public CObjectX
23 {
24 public:
25     void FireClick()
26     {
27         CClickEventArgs args;
28         OnClicked(this, args);
29     }
30 
31     Common::CEvent<int, CObjectX*, CClickEventArgs&> OnClicked;
32 };
33 
34 
35 class CMyClass 
36 {
37 public:
38     int OnBtuttonClicked(CObjectX* pButton, CClickEventArgs& args)
39     {
40         cout << "CMyClass: Receive button clicked event" << endl;
41         return 1;
42     }
43 };
44 
45 int OnBtuttonClicked_C_fun(CObjectX* pButton, CClickEventArgs& args)
46 {
47     cout << "C Style Function: Receive button clicked event" << endl;
48     return 1;
49 }
50 
51 
52 class CMyFunObj
53 {
54 public:
55     int operator()(CObjectX* pButton, CClickEventArgs& args)
56     {
57         cout << "Functor: Receive button clicked event" << endl;
58         return 1;
59     }
60 };
61 
62 int _tmain(int argc, _TCHAR* argv[])
63 {
64     using namespace std::placeholders;
65 
66     CButton btn;
67 
68     CMyClass obj;
69     Common::cookie_type c1 = btn.OnClicked.AddHandler(&obj, &CMyClass::OnBtuttonClicked);
70 
71     Common::cookie_type c2 = btn.OnClicked.AddHandler(OnBtuttonClicked_C_fun);
72 
73     CMyFunObj functor;
74     Common::cookie_type c3 = btn.OnClicked.AddHandler(functor);
75 
76     btn.FireClick();
77 
78 
79     btn.OnClicked.RemoveHandler(c2);
80 
81     std::cout << endl;
82 
83 
84     btn.FireClick();
85 
86     system("pause");
87 
88     return 0;
89 }
90 

以下是测试结果:


 可以看到, 我们在普通C函数, 类成员函数和仿函数(functor)中都测试通过。

另外对于事件函数返回值为void的情况,会编译出错,我们需要偏特化一下:
  1     template< typename T1, typename T2>
  2     class CEvent<void, T1, T2>
  3     {
  4     public:
  5         typedef void return_type;
  6         typedef T1 first_type;
  7         typedef T2 second_type;
  8 
  9         typedef std::function<return_type (first_type, second_type)> handler_type;
 10 
 11         ~CEvent()
 12         {
 13             Clear();
 14         }
 15 
 16         return_type operator()(first_type p1, second_type p2)
 17         {
 18             size_t size = _handlers.size();
 19             for(size_t i=0; i<size; ++i)
 20             {
 21                 _handlers[i]->operator()(p1, p2);
 22             }
 23         }
 24 
 25         cookie_type AddHandler(std::function<return_type (first_type, second_type)> h)
 26         {
 27             CEventHandler*= new(nothrow)  CEventHandler(h);
 28             if(p != nullptr) _handlers.push_back(p);
 29             return (cookie_type)p;
 30         }
 31 
 32         template<typename class_type, typename class_fun>
 33         cookie_type AddHandler(class_type* pThis, class_fun f)
 34         {
 35             CEventHandler* p = new(nothrow) CEventHandler(pThis, f);
 36             if(p != nullptr) _handlers.push_back(p);
 37             return (cookie_type)p;
 38         }
 39 
 40         void RemoveHandler(cookie_type cookie)
 41         {
 42             CEventHandler* p = (CEventHandler*)cookie;
 43 
 44             auto itr = std::find(_handlers.begin(), _handlers.end(), p);
 45             if(itr != _handlers.end())
 46             {
 47                 _handlers.erase(itr);
 48                 delete p;
 49             }
 50             else
 51             {
 52                 assert(false);
 53             }
 54         }
 55 
 56         void Clear()
 57         {
 58             if(!_handlers.empty())
 59             {
 60                 int n = _handlers.size();
 61                 std::for_each(_handlers.begin(), _handlers.end(), [](CEventHandler* p)
 62                 { 
 63                     assert(p != nullptr);
 64                     delete p;
 65                 });
 66                 _handlers.clear();        
 67             }
 68         }
 69 
 70     private:
 71         class CEventHandler 
 72         {
 73         public:
 74             CEventHandler(handler_type h)
 75             {
 76                 _handler = h;
 77                 assert(_handler != nullptr);
 78             }
 79 
 80             template<typename class_type, typename class_fun>
 81             CEventHandler(class_type* pThis, class_fun object_function)
 82             {
 83                 using namespace std::placeholders;
 84                 _handler = std::bind(object_function, pThis, _1, _2);
 85                 assert(_handler != nullptr);
 86             }
 87 
 88             return_type operator()(first_type p1, second_type p2)
 89             {
 90                 assert(_handler != nullptr);
 91                 if(_handler != nullptr) _handler(p1, p2);
 92             }
 93 
 94             handler_type _handler;
 95         };
 96 
 97 
 98     private:
 99         std::vector<CEventHandler*> _handlers;
100     };

最后谈一下在写这个代码中遇到的问题:
(1)不知道你能不能发现下面代码的问题, 我在写代码时就栽在这里了:
  1    vector<int*>  v;
 2    int* p1 = new int(1);
 3    v.push_back(p1);
 4    int* p2 = new int(2);
 5    v.push_back(p2);
 6 
 7    //尝试删除所有值为p1的项
 8    auto itr = remove(v.begin(), v.end(), p1);
 9    for_each(itr, v.end(), [](int* p){delete p;});
10    v.erase(itr, v.end());

(2)我们想把cookei_type放到类里面去, 类似这样:
1     template<typename TR, typename T1, typename T2>
2     class CEvent
3     {
4     public:
5         typedef TR return_type;
6         typedef T1 first_type;
7         typedef T2 second_type;
8         typedef void* cookie_type;

可发现要这样使用:
Common::CEvent<int, CObjectX*, CClickEventArgs&>::cookie_type c1 = btn.OnClicked.AddHandler(&obj, &CMyClass::OnBtuttonClicked);
太不方便了, 不知道大家有没有好的方法。

注:上面的代码还没有经过真正商业使用,如果有问题欢迎指出。
posted on 2013-01-31 14:16 Richard Wei 阅读(288) 评论(4)  编辑 收藏 引用 所属分类: C++

FeedBack:
# re: 在C++中实现事件(委托)
2013-01-31 14:45 | lierlier
有返回值的事件是需要使用返回策略的,单纯返回最后一个值由于回调绑定到事件顺序是不确定的,所以实际上无意义  回复  更多评论
  
# re: 在C++中实现事件(委托)
2013-01-31 14:49 | Richard Wei
@lierlier
确实, 所以.Net里的事件返回类型都都void。这里带返回值的事件适用于只有一个函数绑定。  回复  更多评论
  
# re: 在C++中实现事件(委托)
2013-01-31 16:56 | lierlier
如果不要求判断函数是否已经绑定过了,那么有个简单的做法

这里保持bind在外边可以保留灵活性,比如把三个参数的事件处理函数加上一个默认参数绑定到两个参数的事件上。另外把Event定义为
template<class A1 = NullT, class A2 = NullT,...>
这样的形式,利用偏特化,就可以支持任意个数的事件参数

代码:
#include <map>
#include <functional>

using namespace std;


template<class Arg1, class Arg2>
class Event
{
typedef void HandlerT(Arg1, Arg2);
int m_handlerId;

public:
Event() : m_handlerId(0) {}

template<class FuncT>
int addHandler(FuncT func)
{
m_handlers.emplace(m_handlerId, forward<FuncT>(func));
return m_handlerId++;
}

void removeHandler(int handlerId)
{
m_handlers.erase(handlerId);
}

void operator ()(Arg1 p1, Arg2 p2)
{
for ( const auto& i : m_handlers )
i.second(p1, p2);
}

private:
map<int, function<HandlerT>> m_handlers;
};

void f1(int, int)
{
printf("f1()\n");
}

struct F2
{
void f(int, int)
{
printf("f2()\n");
}

void operator ()(int, int)
{
printf("f3()\n");
}
};

int _tmain(int argc, _TCHAR* argv[])
{
Event<int, int> e;

int id = e.addHandler(f1);

e.removeHandler(id);

using namespace std::placeholders;

F2 f2;

e.addHandler(bind(&F2::f, f2, _1, _2));
e.addHandler(bind(f2, _1, _2));

e.addHandler([](int, int) {
printf("f4()\n");
});

e(1, 2);

return 0;
}  回复  更多评论
  
# re: 在C++中实现事件(委托)
2013-01-31 17:36 | Richard Wei
@lierlier
多谢指点,感觉你的实现方式对外界比较灵活,性能也比较高。
而我的实现更强调封装,适合于在某个框架中使用。  回复  更多评论
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
标准库 STL :Allocator能做什么?
stl_alloc.h
(原创)C IOC框架
std::unique_ptr使用incomplete type的报错分析和解决
久别重逢的 std::bad_alloc
C++模板元编程(C++ template metaprogramming)
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服