摘要: 介绍了事件委托机制的需求,各种解决方案的演变,最终提出模板化的事件委托机制,并给出较详细的进化过程和原理说明。
关键词: C++,委托,委托器,事件器,模板
第一章 基础版实现
在平时的工作中,我们经常会遇到以下情况
void Do(int event_id)
{
…
}
void OnEvent(int event_id)
{
Do(event_id);
}
下面是成员函数版本
class A
{
public:
void Do(int event_id)
{
…
}
};
class B
{
public:
void OnEvent(int event_id)
{
a.Do(event_id);
}
private:
A a;
};
(这里a或者是B的成员,或者是全局变量,或者通过OnEvent函数传递进来)
以上是一般情况,当B的OnEvent还需要调用另外的函数或者其他对象的函数时,就不得不对OnEvent函数作出改动,当然如果A的类型改变了,也要做相应改动,变成
void OnEvent(int event_id)
{
c.Run(event_id);
}
或者
void OnEvent(int event_id)
{
a.Do(event_id);
c.Run(event_id);
…
}
由于需求的多变性,导致OnEvent函数面对不同的情况有不同的实现,类B的复用性大大降低。我们知道GUI是接收事件并作出处理的一个典型例子,如果按照以上方法,则每一种控件都需要被继承,重载OnEvent函数,用以对应不同的事件响应,是一件很可怕的任务,
:(
第二章 多态版实现
2.1 单任务的实现
C++提供了多态机制,我们可以使用类的虚函数改善以上的问题。
(在C中可以使用函数指针的方法,其本质是相同的,这个就由读者自己发挥了)
class EventCallerBase
{
public:
// 基类使用纯虚函数,派生类必须实现
virtual void Do(int event_id) = 0;
};
class Receiver
{
public:
void SetEventCaller(EventCallerBase* pCaller) { m_pCaller = pCaller; }
void OnEvent(int event_id)
{
if (m_pCaller)
m_pCaller->Do(event_id);
}
private:
EventCallerBase* m_pCaller;
};
class EventCallerA : public EventCallerBase
{
public:
virtual void Do(int event_id)
{
printf("EventCallerA do event %d.\r\n", event_id);
}
};
void main()
{
EventCallerA caller;
Receiver receiver;
receiver.SetEventCaller(&caller);
…
receiver.OnEvent(99);
}
输出:EventCallerA do event 99.
2.2 多任务的实现
对于需要对多个对象调用其函数的情况,用以下方式
EventCallerBase,EventCallerA的实现同上
class EventCallerB : public EventCallerBase
{
public:
virtual void Do(int event_id)
{
printf("EventCallerB do event %d.\r\n", event_id);
}
};
class Receiver
{
public:
void AddEventCaller(EventCallerBase* pCaller)
{
if (pCaller)
m_CallerList.push_back(pCaller);
}
void OnEvent(int event_id)
{
list<EventCallerBase*>::iterator it = m_CallerList.begin();
while (it != m_CallerList.end())
{
EventCallerBase* pCaller = *it;
if (pCaller)
pCaller->Do(event_id);
++it;
}
}
private:
list<EventCallerBase*> m_CallerList;
};
void main()
{
EventCallerA callerA;
EventCallerB callerB;
Receiver receiver;
receiver.AddEventCaller(&callerA);
receiver.AddEventCaller(&callerB);
…
receiver.OnEvent(99);
}
输出:EventCallerA do event 99.
EventCallerB do event 99.
在以上方法中,类Receiver基本做到了重用,除了OnEvent参数类型和个数的改变,一般情况下,当有事件发生,调用不同的事件处理函数时,只要继承EventCallerBase类,实现Do函数,并在初始阶段设定AddEventCaller即可。这种方法在GUI中,已经能尽可能地重用发生事件部分的类和代码,把主要工作放在实现事件响应的处理上。
2.3 对已有类的改造
这里有个问题,如果有一个需求,比如窗口最大化,需要调用成员函数System::Maximize(),怎么办?类System是一个既有类,不能随便改动,来继承EventCallerBase。上面的方法岂不是不实用?
小小地动动脑筋,方法是有的:
class System
{
public:
void Maximize(void) { printf("Window is maximized.\r\n"); }
};
class EventCallerSystem : public EventCallerBase
{
public:
EventCallerSystem(System* pSystem) { m_pSystem = pSystem; }
virtual void Do(int event_id)
{
if (m_pSystem)
m_pSystem->Maximize()
}
private:
System* m_pSystem;
};
void main()
{
System system;
EventCallerSystem callerSystem(&system);
Receiver receiver;
receiver.AddEventCaller(&callerSystem);
…
receiver.OnEvent(99);
}
输出:Window is maximized.
解决了问题,还留了一个小尾巴,就是要多实现一个EventCallerSystem类。
有没有办法把这个小尾巴也一并解决掉呢,这就到了这篇文章的主题――C++中的事件委托机制,这次我们用到了C++的另一个特性---模板。
第三章 事件委托版实现
3.1 函数指针的使用
我们首先复习一下函数指针和成员函数指针。
3.1.1 函数指针
在C和C++语言中,一个命名为my_func_ptr的函数指针指向以一个int和一个char*为参数的函数,这个函数返回一个浮点值,声明如下:
float (*my_func_ptr)(int, char *);
为了便于理解,一般我们使用typedef关键字。
typedef float (*MyFuncPtrType)(int, char *);
如果你的函数指针指向一个型如float some_func(int, char *)的函数,这样做就可以了:
MyFuncPtrType my_func_ptr = some_func;
当你想调用它所指向的函数时,可以这样写:
(*my_func_ptr)(7, "HelloWorld");
或者
my_func_ptr(7, "HelloWorld");
3.1.2 成员函数指针
在C++程序中,很多函数是成员函数,即这些函数是某个类中的一部分。你不可以像一个普通的函数指针那样指向一个成员函数,正确的做法应该是,你必须使用一个成员函数指针。一个成员函数的指针指向类中的一个成员函数,并有相同的参数,声明如下:
float (SomeClass::*my_memfunc_ptr)(int, char *);
将函数指针指向型如float SomeClass::some_member_func(int, char *)的函数,可以这样写:
my_memfunc_ptr = &SomeClass::some_member_func;
当你想调用它所指向的成员函数时,可以这样写:
SomeClass* x = new SomeClass;
(x->*my_memfunc_ptr)(6, "HelloWorld");
3.2 函数指针的大小
class A
{
public:
int Afunc() { return 2; };
};
class B
{
public:
int Bfunc() { return 3; };
};
class D: public A, public B
{
public:
int Dfunc() { return 5; };
};
int main()
{
printf("%d\n", sizeof(&main));
printf("%d\n", sizeof(&A::Afunc));
printf("%d\n", sizeof(&B::Bfunc));
printf("%d\n", sizeof(&D::Dfunc));
return 0;
}
输出:
4
4
4
8
可以看出,普通函数的指针大小是4,
普通类的成员函数的指针大小也是4,
对于多重继承的类,成员函数的指针大小是8,
还有成员函数指针大小是12和16的情况,在这里就不展开了。
(需要特别注意的是,相同的代码,在不同的编译器下,函数指针的大小也不相同)。
对函数指针和成员函数指针的复习就到这里。
3.3 C++中的事件委托
以下登场的是本文的主角:模板化实现的C++中的事件委托
3.3.1代码
/////////////////////////////////////////////////////////////////////////////////
/// \class FuncCache
/// \brief 函数对象寄存器
/////////////////////////////////////////////////////////////////////////////////
template <typename ReturnType>
class FuncCache
{
static const int SIZE = 48;
typedef ReturnType (*func_caller)(FuncCache*);
/// \class MemberFuncAssist
/// \brief 对象成员函数寄存器的辅助器
class FuncCacheAssist
{
public:
/// \brief 构造函数,初始化。
FuncCacheAssist(FuncCache* pFunc)
{
m_Size = 0;
m_pFunc = pFunc;
// 读取用偏移必须归位
m_pFunc->m_Cur = 0;
}
/// \brief 析构函数。
~FuncCacheAssist(void)
{
// 弹出以前压入的参数
if (m_Size > 0)
m_pFunc->Pop(m_Size);
}
/// \brief 压入指定大小的数据。
uint Push(const void* pData, uint size)
{
m_Size += size;
return m_pFunc->Push(pData, size);
}
/// 压入参数的大小
int m_Size;
/// 对象成员函数寄存器
FuncCache* m_pFunc;
};
public:
/// \brief 构造函数,初始化。
FuncCache(func_caller func)
{
m_Size = 0;
m_Cur = 0;
m_Func = func;
}
/// \brief 压入指定大小的数据。
uint Push(const void* pData, uint size)
{
size = (size <= SIZE - m_Size)? size : (SIZE - m_Size);
memcpy(m_Buffer + m_Size, pData, size);
m_Size += size;
return size;
}
/// \brief 弹出指定大小的数据。
uint Pop(uint size)
{
size = (size < m_Size)? size : m_Size;
m_Size -= size;
return size;
}
/// \brief 读取指定大小的数据,返回指针。
void* Read(uint size)
{
m_Cur += size;
return (m_Buffer + m_Cur - size);
}
/// \brief 执行一个参数的函数。
ReturnType Execute(const void* pData)
{
// 用辅助结构控制
FuncCacheAssist assist(this);
// 压入参数
assist.Push(&pData, sizeof(void*));
// 执行函数
return m_Func(this);
}
protected:
/// 对象,函数,参数指针的缓冲区
uchar m_Buffer[SIZE];
/// 缓冲区大小
uint m_Size;
/// 缓冲区读取用的偏移
uint m_Cur;
/// 操作函数的指针
func_caller m_Func;
};
/////////////////////////////////////////////////////////////////////////////////
/// \class MFuncCall_1
/// \brief 一个参数的成员函数执行体
/////////////////////////////////////////////////////////////////////////////////
template <typename ReturnType, typename Caller, typename Func, typename ParamType>
class MFuncCall_1
{
public:
/// \brief 执行一个参数的成员函数。
static ReturnType MFuncCall(FuncCache<ReturnType>* pMFunc)
{
// 获得对象指针
Caller* pCaller = *(Caller**)pMFunc->Read(sizeof(Caller*));
// 获得成员函数指针
Func func = *(Func*)pMFunc->Read(sizeof(Func));
// 获得参数的指针
ParamType* pData = *(ParamType**)pMFunc->Read(sizeof(ParamType*));
// 执行成员函数
return (pCaller->*func)(*pData);
}
};
/////////////////////////////////////////////////////////////////////////////////
/// \class L_SignalRoot
/// \brief 类型检查严格的事件委托器基类
/////////////////////////////////////////////////////////////////////////////////
template <typename ReturnType>
class L_SignalRoot
{
public:
/// \brief 指定事件名,卸载指定对象的事件委托器。
template <typename Caller>
void MFuncUnregister(Caller* pCaller)
{
func_map& func_list = m_MemberFuncMap;
func_map::iterator it = func_list.find(pCaller);
if (it != func_list.end())
func_list.erase(it);
}
/// \brief 清空所有事件委托器。
void MFuncClear(void)
{
m_MemberFuncMap.clear();
}
protected:
typedef map< void*, FuncCache<ReturnType> > func_map;
/// 事件名和绑定的事件委托器的列表
func_map m_MemberFuncMap;
};
/////////////////////////////////////////////////////////////////////////////////
/// \class L_Signal_1
/// \brief 类型检查严格,一个参数的事件委托器
/////////////////////////////////////////////////////////////////////////////////
template <typename ReturnType, typename ParamType>
class L_Signal_1 : public L_SignalRoot<ReturnType>
{
public:
/// \brief 指定事件名,注册对应的一个参数的事件委托器。
template <typename Caller, typename Func>
void MFuncRegister(Caller* pCaller, Func func)
{
// 指定专门处理一个参数的函数执行体
FuncCache<ReturnType> mfunc(MFuncCall_1<ReturnType, Caller, Func, ParamType>::MFuncCall);
// 压入对象和函数
mfunc.Push(&pCaller, sizeof(Caller*));
mfunc.Push(&func, sizeof(Func));
// 添加到事件委托器列表
m_MemberFuncMap.insert(make_pair(pCaller, mfunc));
}
/// \brief 指定事件名,调用其对应的一个参数的事件委托器。
ReturnType MFuncCall(const ParamType& data)
{
// 清空返回值
ReturnType result;
memset(&result, 0, sizeof(result));
// 对于所有委托器,调用注册的函数
func_map::iterator it = m_MemberFuncMap.begin();
while (it != m_MemberFuncMap.end())
{
result = it->second.Execute(&data);
++it;
}
return result;
}
};
class EventCallerA
{
public:
bool Do(int event_id)
{
printf("EventCallerA do event %d.\r\n", event_id);
return true;
}
};
class EventCallerB
{
public:
bool Run(int event_id)
{
printf("EventCallerB run event %d.\r\n", event_id);
return true;
}
};
void main()
{
// 申明返回值是bool类型,参数是int类型,单参数的事件器
L_Signal_1<bool, int> signal;
EventCallerA callerA;
EventCallerB callerB;
// 注册委托器并调用事件
signal.MFuncRegister(&callerA, &EventCallerA::Do);
signal.MFuncRegister(&callerB, &EventCallerB::Run);
signal.MFuncCall(1);
}
注意这里EventCallerA和EventCallerB并没有相同的基类。
3.3.2 名词定义
先定义一些概念,便于我们统一理解
事件器:指发生事件后,处理事件的响应,逐个通知事先注册的对象。
委托器:指某事件发生后,需要被通知,并执行事先注册的函数的对象。
3.3.3 需求
再谈谈我们的需求:
1. 某个事件发生后,能通知到所有事先注册过的委托器。
2. 委托器的类型可能千差万别。
3. 加入参数使这个机制更灵活,应对每次不同的事件参数,支持1个,2个,甚至更多的参数。
4. 参数的类型也不希望有限制。
5. 委托器有执行结果,可以被事件器获取。
6. 委托器销毁的时候,需要通知事件器,将其从委托器列表中排除。
3.3.4 限制
最后谈谈可以有的限制
1. 针对同一个事件,委托器的函数参数类型应该是相同的,顺序也相同,因为事件的参数类型是不变的,否则可以分解为两个事件。由事件起始,通知委托器,如果参数类型各不相同,没有意义。
2. 函数参数过多也没有意义,因为我们知道,多个参数的需求可以用类或者结构体代替,以减少参数个数。
3. 大多数情况下,我们只需要知道最后一个委托器的执行结果。
3.3.5 解决方法
怎么办?抽象!
如何抽象??往二进制层面抽象!!
当我们要统一处理一些需求的时候,我们只有把需求看成相同的类型和格式。
说到底,对象只是内存中的一块数据,而函数也是内存中的一段数据,我们可以用内存地址的方式来统一表示它们。
3.3.6 代码解析
模板类FuncCache 就是为了实现这一级的抽象而存在,以下对FuncCache做必要解析:
FuncCache::m_Buffer用来存储对象的指针,成员函数的指针,以及函数参数的指针,暂定大小为固定48字节,其中对象指针4字节,成员函数指针4到16字节不等,参数指针每个为4字节,可以有多个。
FuncCache::m_Size表示目前用到了多少字节的数据。
FuncCache::m_Cur表示用来从头依次读取对象指针,成员函数指针和参数指针的数据偏移量。
FuncCache::m_Func很关键,因为以上都是数据,光有材料还要明确如何处理,其就承担了这个重要的任务,类型是typedef ReturnType (*func_caller)(FuncCache*),对于同种ReturnType,其类型是固定的,这是很关键的一步,完成了从不同类型的对象,不同类型的函数以及参数(有条件的)到一致的对象之间的抽象。
FuncCache的各个函数很简单,不做详细说明了,值得一提的是其内部类FuncCacheAssist,这个内部类存在的主要价值是在Execute退出的时候,将压入的参数排除。
接下来解析模板类MFuncCall_1
这里只是列了对于一个参数的函数委托器的实现,无参数、多参数的实现类似,请读者自行发挥。
该模板类很简单,只有一个函数,但是提供了很灵活的功能,返回类型,对象类型,成员函数类型,参数类型,全部可以自定义的,也只有通过模板类才能实现所需要的功能,具体函数算法很简单,就不展开了。
最后是模板类L_SignalRoot和L_Signal_1,也就是事件器。
ReturnType是模板化的返回类型,ParamType是模板化的单参数类型。
该类只有一个变量m_MemberFuncMap,用来保存所有的委托器的抽象,也就是FuncCache。
该类提供了四个接口,也是对于使用者最常使用的:
1. MFuncRegister 注册委托器对象和函数
2. MFuncCall 调用所有注册的委托器,并返回最后调用的结果。
3. MFuncUnregister 根据对象指针,删除注册过的委托器,委托器在事件器之前销毁的话, 必须调用这个接口,这点没有做成自动的,主要为了减少类的复杂度。
4. MFuncClear 清空所有委托器。
给用户的接口很简单明了易于使用。
C++实现委托,事件原理讲解
时间: 2024-07-31 11:10:00
C++实现委托,事件原理讲解的相关文章
c#委托事件及其讲解
一定要标明出处,波哥的文章.所有文章都值得一看.这篇是摘抄的大白话之C#事件讲解.委托 http://www.cnblogs.com/wudiwushen/archive/2010/04/20/1703368.html 例子是小明委托小明去买电影票 c#语法如下: public delegate void BugTicketEventHandler(); delegate 是关键词,[注:EventHandler是一个声明委托的微软C#的命名标准,我的习惯是标准就要第一时间说,也就这个命名习惯要
js事件委托及其原理
1,什么是事件委托:通俗的讲,事件就是onclick,onmouseover,onmouseout,等就是事件,委托呢,就是让别人来做,这个事件本来是加在某些元素上的,然而你却加到别人身上来做,完成这个事件. 也就是:利用冒泡的原理,把事件加到父级上,触发执行效果. 好处呢:1,提高性能. 我们可以看一个例子:需要触发每个li来改变他们的背景颜色. <ul id="ul"> <li>aaaaaaaa</li> <li>bbbbbbbb&l
jquery事件委托的原理和用途
事件委托 声明:这一篇文章只是阐述了委托事件的原理和用委托的好处还有就是在什么时候要委托合适.在这里用的是on()和off()而对于委托的其他方法(delegate().live()等)和各个方法的比较在这里并没有详细阐述,以后如果有机会在发一篇 1.什么是委托呢? a)定义:把一件事交给别人代做就叫做委托 b)问题:为什么要把事交给别人来做呢? 原因很简单:第一.自己不想做,所以交给别人 第二.自己不会做,所以交给别人来做 第三.让别人来做这一件事更有优势等等(这也是为什么要使用委托的原因)
js的事件的三个阶段,事件委托的原理
DOM2级事件规定的事件流的三个阶段:捕获,目标,冒泡(IE8以及更早版本不支持DOM事件流); 事件流: IE:IE事件流是事件冒泡流 Netscape事件流是事件捕获流 IE事件流 叫做事件冒泡,即事件开始时由最具体的元素(文档中嵌套最深的那个节点)接收,然后逐级向上(一直到文档).事件捕获与事件冒泡事件流正好相反的顺序,事件捕获的事件流是最外层逐级向内传播. 使用DOM0级方法指定的事件处理程序被认为是元素的方法,处理程序是在元素的作用域进行的,程序中this是引用的是当前元素 btn
0173 事件委托:原理、作用
事件冒泡本身的特性,会带来的坏处,也会带来的好处. 1.3.8.1 什么是事件委托 事件委托:也称为事件代理,在 jQuery 里面称为事件委派. 把事情委托给别人,代为处理. 说白了就是,不给子元素注册事件,给父元素注册事件,把处理代码在父元素的事件中执行. 生活中的代理: js事件中的代理: 1.3.8.2 事件委托的原理 ? 给父元素注册事件,利用事件冒泡,当子元素的事件触发,会冒泡到父元素,然后去控制相应的子元素[利用事件冒泡影响每一个子节点]. 1.3.8.3 事件委托的作用 我们只操
C#解惑1——委托&;事件(转)
委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的可扩展性. 委托和事件在.NET Framework[1]中的应用非常广泛,然而,较好地理解委托和事件对很多接触C#时间不长的人来说并不容易. 中文名 c#委托 外文名 Delegate 编程语言 C# 作 用 方便的引用被委托的方法 应 用 NET Framework 目录 1引言 2参数
仿今日头条下拉出现SearchBar,再下拉刷新效果,SearchListView实现以及原理讲解
先看效果 分别是我的效果和今日头条的效果: 以上效果包括: 1.如果下拉的高度超过search view的高度的3/4,但是小于head view高度,则松开手时search view自动出现 2.如果下拉的高度小于search view的高度的1/4,则松开手时search view自动回弹消失 3.如果下拉的高度超过head view的总高度,则松手进行刷新 4.刷新完成自动隐藏search view 实现原理讲解 参考了 github开源项目:[https://github.com/viv
理解jquery on 委托事件的机制
前两天做了一个点击任意位置,都能关闭菜单的功能,因为菜单里面的每一个a,的点击事件都是用on绑定的.所以在阻止冒泡的时候不管用,今天特意来理解一下on的机制 on 是委托事件,利用的就是冒泡原理 $(selector).on(event,[selector],handler); selector 如果为空,表示的就是目标元素就是所选元素,如果有值代表的是符合所选元素的所有后代元素 实际上,事件委托的事件处理函数是当被委托的元素上的事件触发时判断e.target后执行,而不是目标元素上的事件被触发
delegate委托事件(动态创建元素注册事件)
有这样一个小例子: <!--需求:给li里的a标签注册点击事件,并且点击"添加"按钮,新增li标签,新增的li里的a同样有注册事件--> <input type="button" id="btn" value="添加"/> <ul class="box"> <li> <a href="javascript:void(0)">点击