首页 > 代码库 > C++实现委托,事件原理讲解
C++实现委托,事件原理讲解
摘要: 介绍了事件委托机制的需求,各种解决方案的演变,最终提出模板化的事件委托机制,并给出较详细的进化过程和原理说明。
关键词: 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)
{
关键词: 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)
{