当前课程知识点:基于Linux的C++ > 第十一讲 泛型编程 > 11.17 泛型编程实践(九) > LinuxCPP1117
接下来一节
我们提供一个工程实践的样例
就是事件机制
什么叫事件呢
事件是指操作系统和应用程序中
发生了某件事情
程序的某一个组件需要响应这个事件
并进行特定的处理
这个处理的方案和方式就叫事件机制
在面向对象的架构中
事件响应函数最可能的是成员函数
没有面向对象技术之前
怎么处理这个事件呢
我们只有一个方案 使用函数指针
有了面向对象架构以后
事件响应函数最可能的就是成员函数
那这就会导致一个问题
就是指向类的成员函数的指针
它不能转型成哑型指针
这一点是和普通的函数指针是不一样的
因为它不仅仅包括了那个成员函数的特性
因为它不仅仅包括了那个成员函数的特性
还包括了那个类的特性
所以指向类成员函数的指针和哑型指针
不能够自如地互相转化
不能转型 那就有问题
因为我们响应这个事件的
将会是某一个对象的成员函数
而这个对象从属于哪个类
不同的时候是不一样的
成员函数名字也有可能是不一样的
只要那个对象不一样
这个事情就大条了
对吧 你就处理不了了
转成哑型指针肯定是不成了嘛
所以这里面会有一个大的问题
同时更严格讲起来
其实你这个指向类成员函数的指针
不仅不能转型成哑型指针
它也不能转型成另外一个类的成员函数的指针
它也不能转型成另外一个类的成员函数的指针
有的编译器允许你在类的继承层次上面
将指向类的成员函数的指针进行转型
如果没有继承层次呢
它也不能转型 再说这个事儿
也不一定所有的编译器都支持
所以这实际上是一个很成问题的地方
那么我们怎么实现呢 有一个诀窍
就是当你用对象无法实现的时候
那么你就用指向对象的指针来实现它
当你用指向对象的指针无法实现的时候
那么你就用指向对象的指针的指针
来实现它 你就应该用
指向指向对象的指针的指针来实现它
用一个二级指针
这是非常重要的技巧
所以在我们 C++ 里边
实现事件机制的时候
那么我们在工程实践的场合
需要借助于模板的概念
形成我们的事件委托模型
我会定义一个 Event 一个类模板
用它来管理我们的事件响应者对象
实现事件多播
我们有一个 EventResponsor 这样的一个类模板
我们有一个 EventResponsor 这样的一个类模板
它对应于响应者对象与响应者行为的配对
它对应于响应者对象与响应者行为的配对
也就是事件响应者是谁
它的响应行为是哪一个
这就形成一个配对
我们用 Empty 这样一个空类
来做委托模型和指针转换
我们来看具体的代码实现
空类 “class Empty { };” 什么也没有
它就用于指代我们的响应者对象
我们刚才不是讲了吗
你不能使用指向类成员函数的指针
对吧 我们的响应函数是明确的
但是你不能做这样的转型
所以我们需要使用一个空类
来保存那个指向类的成员函数的指针中的对象那部分
来保存那个指向类的成员函数的指针中的对象那部分
接下来这个类模板就是事件响应者
template〈typename EventAction〉 class EventResponsor
它带的一个模板的形式参数是 EventAction
事件活动 每一个事件响应者模板
它都保存着特定事件的响应者和它对应的响应行为
它都保存着特定事件的响应者和它对应的响应行为
所以它包括两个字段
这两个字段为了处理的方便
我们都把它公开了
一个是指向 Empty 类的一个指针 actor
就是行动者
一个是指向 EventAction 的一个指针 action
这就是它的响应行为
为什么要这么设计呢 刚才那么讲的
一个普通的函数指针转型成一个哑型指针是没有问题的
一个普通的函数指针转型成一个哑型指针是没有问题的
可是一个指向类的成员函数的指针是不能够转型为哑型指针的
可是一个指向类的成员函数的指针是不能够转型为哑型指针的
为什么呢
就是因为指向类成员函数的指针
它不是普通的指针
首先它有一个指向那个类的对象的指针标记
首先它有一个指向那个类的对象的指针标记
其次它有那个成员函数
在那个对象中的那个属性的描述
它实际上在实现上都不一定是指针
尺寸都有可能是变化的
所以这实现策略和哑型指针
是完全不一样的两回事
你不能够把它转型
那么我们这里面要做的一件事情
就是在 EventResponsor 里边记录
记录什么呢
你不能记录指向类成员函数的指针
但是我可以记录指向指向类成员函数的指针的指针 这个就是它
但是我可以记录指向指向类成员函数的指针的指针 这个就是它
我记录那个指向类成员函数指针的地址
而不是它真实指向的那个类的成员函数
EventAction * action
action 记录的就是它
而 actor 记录的就是那个对象
哪一个行动者做这件事儿
做的就是这个对象
当我们最终响应这个行为的时候
我们将在 actor 这个对象上做这个 action
就用这两个属性完成最终的
事件响应机制的响应者行为和响应者对象的配对和拼接
事件响应机制的响应者行为和响应者对象的配对和拼接
对 EventResponsor 来讲
它有一个缺省的构造函数
actor 把它初始化化成 NULL
action 也初始化成 NULL 空值
还有一个二值构造函数
EventResponsor( Empty *, EventAction * )
就直接拷贝就完了
EventResponsor 这个类并不负责
action 和 actor 这两个数据字段的存储问题
虽然它们是指针
但是 EventResponsor 并不负责它
我们会有一个专门的事件类来处理这个指针问题
我们会有一个专门的事件类来处理这个指针问题
EventResponsor 本身并不负责
这两个指针的动态分配与释放的问题
跟它没关系
有一个友元函数 operator==
判定两个事件响应者是不是相等的
注意我们比较的时候是不一样的
如果是 actor
只要比较两者是不是相等就行了
因为它们都是指向那个对象的指针
所以只要比较两者值是不是相等
就能够得到它们是不是指向同一个对象
而后面那个 action 呢 存的是二级指针
是指向指向类成员函数的指针的指针
所以我们要比较的时候
必须先引领 然后再比较
才能看它们指向的是不是同样的一个类的成员函数
才能看它们指向的是不是同样的一个类的成员函数
注意这个实现
就这一点是要注意的地方
这是我们的 EventResponsor
然后是我们的事件类模板 class Event
前边的模板是 template〈typename EventAction〉
还是事件活动
Event 我们定义两个型
因为我们是使用一个向量
来构造所有响应同样的事件的响应者的
一个事件的响应者有很多个的
所以我用一个向量来保存它
vector 这个类型太长了
vector〈〉里面是什么呢
〈〉就是那个事件响应者的类型
事件响应者的类型是什么呢
那玩意儿也是一个模板
所以你得写 EventResponsor〈EventAction〉
你看你写的 vector〈 EventResponsor〈EventAction〉〉
很费劲的 很长的一串
每次都那么写 烦死人
所以我们用 typedef 给它定义出来
形成叫 EventResponsors
表示一个事件响应者集合
叫事件响应者们 好吧
我们就用它来替换这么长的一个类型的标记
我们就用它来替换这么长的一个类型的标记
你如果觉得这个不够长
那你看下面这一个呢
后面还有 “::iterator” 呢
你还要取它的迭代器呢
这就是事件迭代器
那不就更长了嘛
所以我们也把它定义出来
有一点是需要说明的
就是早期的 C++ 编译器
它没有办法解析连续的模板右箭头
它会认为这是一个输入操作符
它会搞混淆的 所以必须加空格
C++11 以后的编译器都能够处理这个问题
不加空格也行了
有的时候 在模板中
我们很难决定一个特定的名字
它到底是一个型还是一个量
所以如果你想明确地
告诉编译器这是一个型 而不是一个量
可以在这个型的名字前边加上 typename
有一个虚析构函数
用一个事件迭代器去迭代这个事件向量
一个接着一个的删除它的事件响应者 就完了
删除它的响应者的时候
因为事实上我们只动态构造一个东西
就是 action 的那个指针
所以我们只要销毁它就行了
它析构的时候 剩下的向量本身
由这个事件这个类自动地替我们析构
最重要的两个函数来了
第一个 事件绑定
在我这个事件类里边
我使用一个向量来保存这个特定事件的所有响应者
我使用一个向量来保存这个特定事件的所有响应者
那么每一个响应者来了以后
就要绑定到这个对应的事件上
将来这个事件发生以后 它才能够响应
就是插入到我们这个向量的列表里边去
那么 Bind() 这个函数就应该这么写
它同样是有一个模板
template〈typename Responsor, typename Action〉
传两个模板的形式参数
一个是 Responsor 响应者
一个是响应者的活动 Action
它们的类型
注意和我们前面那个并不完全匹配的
它也并不需要完全匹配
这只是模块的形式参数
void Bind( Responsor * actor, Action action )
那个活动 本身就是一个指针了
它是指向类成员函数的指针
所以不需要再用指针来了
我们传进来不需要
我们内部会 new 它的
看我们的代码实现
把 act 定义成指向 Action 的一个指针量
然后 new 一个 Action
用小 action 去构造它 去初始化
构造一个指向指向类成员函数的指针的指针
然后我们通过传进来的 actor
和新构造出来的这个 act 来构造一个
EventResponsor〈EventAction〉类的一个对象 er
要把 actor 转型成 Empty *
要把 act 转型成 EventAction *
然后构造这个对象 er
定义一个 bool 量 unbound
设为 true 就是未绑定
一开始是未绑定的 迭代
查找这个事件向量的列表
一个接着一个地去比对
看看我们新构造的这个 er 对象
是不是在我们的事件向量里边
已经存过一次了
什么时候知道它存了还是没存呢
那么就在循环里边
你迭代的时候看 *it 和 er 是不是相等的
*it == er
我们不是重载了 operate== 吗
所以 *it == er 就表示这个事件响应者对象
已经被我们在向量列表中绑定进去了
已经有了 actor 是一样的
它对应的 action 也是一样的
那可不就已经有了嘛
所以这种时候 我们把它 unbound 设置成 false 我就 break
所以这种时候 我们把它 unbound 设置成 false 我就 break
就不用再朝下做
如果是 unbound 没有绑定
我们就把这个新的这个 er 对象
插入到我们的向量里
否则就销毁我们新构造的这个 act 内存区域
否则就销毁我们新构造的这个 act 内存区域
这是我们的 Bind() 函数
相对应的是 Unbind()
我要解除事件绑定 模板还一样
template〈typename Responsor, typename Action〉 void Unbind( Responsor * actor, Action action )
template〈typename Responsor, typename Action〉 void Unbind( Responsor * actor, Action action )
同样地 我要 new 一个 Action 出来
用 act 表达它
构造一个 EventResponsor〈EventAction〉类的一个对象 er
构造一个 EventResponsor〈EventAction〉类的一个对象 er
同样要做转型构造 同样我们需要迭代
同样我们需要去查找
当我找到 *it==er 的时候
说明我们需要将这个对象
从事件响应队列里边给它摘出来
解除绑定嘛 把它摘出来
所以我们要 delete it->action
然后把 this->_ers.erase( it )
把这个迭代器所指向的这个对象
从向量中给擦掉 我不要了
删除它 然后 break
不管怎么样 最后要删除我们
新构造出来的这个 act 那个对象
这个就是我们的 Unbind() 函数
代码看上去有点小复杂
Event 这个类最后只有一个私有的数据成员
EventResponsors _ers 事件响应者们
某一个特定的事件
它有一个对应的事件响应向量
这里边包括了很多个事件响应者
每一个事件响应者
都会对这个事件做出响应
只要你把那个事件响应者
绑定到这个事件里来
就是插入到这个事件这个类的那个对象
EventResponsors _ers 里去
就能够做到这一条
-1.1 提纲
-1.2 程序设计的基本概念
-1.3 简单C/C++程序介绍
-1.4 程序设计的基本流程
-1.5 基本语法元素
-1.6 程序设计风格
-1.7 编程实践
-第一讲 C/C++基本语法元素--编程实践提交入口
-2.1 提纲
-2.2 结构化程序设计基础
-2.3 布尔数据
-2.4 分支结构
-2.5 break语句
-2.6 循环结构
-2.7 编程实践
-第二讲 程序控制结构--编程实践提交入口
-3.1 提纲
-3.2 函数声明、调用与定义
-3.3 函数调用栈框架
-3.4 编程实践
-第三讲 函数--编程实践提交入口
-4.1 提纲
-4.2 算法概念与特征
-4.3 算法描述
-4.4 算法设计与实现
-4.5 递归算法(一)
-4.6 递归算法(二)
-4.7 容错与计算复杂度
-4.8 编程实践
-第四讲 算法--编程实践提交入口
-5.1 提纲
-5.2 库与接口
-5.3 随机数库(一)
-5.4 随机数库(二)
-5.5 作用域与生存期
-5.6 典型软件开发流程(一)
-5.7 典型软件开发流程(二)
-5.8 编程实践
-第五讲 程序组织与开发方法--编程实践提交入口
-6.1 提纲
-6.2 字符
-6.3 数组(一)
-6.4 数组(二)
-6.5 结构体
-6.6 编程实践
-第六讲 复合数据类型--编程实践提交入口
-7.1 提纲
-7.2 指针基本概念
-7.3 指针与函数
-7.4 指针与复合数据类型(一)
-7.5 指针与复合数据类型(二)
-7.6 字符串
-7.7 动态存储管理(一)
-7.8 动态存储管理(二)
-7.9 引用
-7.10 编程实践
-第七讲 指针与引用--编程实践提交入口
-8.1 提纲
-8.2 数据抽象(一)
-8.3 数据抽象(二)
-8.4 链表(一)
-8.5 链表(二)
-8.6 链表(三)
-8.7 链表(四)
-8.8 函数指针(一)
-8.9 函数指针(二)
-8.10 抽象链表(一)
-8.11 抽象链表(二)
-8.12 编程实践
-第八讲 链表与程序抽象--编程实践提交入口
-9.1 提纲
-9.2 程序抽象与面向对象
-9.3 类类型
-9.4 对象(一)
-9.5 对象(二)
-9.6 类与对象的成员(一)
-9.7 类与对象的成员(二)
-9.8 类与对象的成员(三)
-9.9 继承(一)
-9.10 继承(二)
-9.11 继承(三)
-9.12 多态(一)
-9.13 多态(二)
-9.14 编程实践
-第九讲 类与对象--编程实践提交入口
-10.1 提纲
-10.2 四则运算符重载(一)
-10.3 四则运算符重载(二)
-10.4 关系与下标操作符重载
-10.5 赋值操作符重载(一)
-10.6 赋值操作符重载(二)
-10.7 赋值操作符重载(三)
-10.8 赋值操作符重载(四)
-10.9 赋值操作符重载(五)
-10.10 流操作符重载(一)
-10.11 流操作符重载(二)
-10.12 流操作符重载(三)
-10.13 操作符重载总结
-10.14 编程实践
-第十讲 操作符重载--编程实践提交入口
-11.1 提纲
-11.2 泛型编程概览
-11.3 异常处理机制(一)
-11.4 异常处理机制(二)
-11.5 运行期型式信息(一)
-11.6 运行期型式信息(二)
-11.7 模板与型式参数化
-11.8 题外话:术语翻译
-11.9 泛型编程实践(一)
-11.10 泛型编程实践(二)
-11.11 泛型编程实践(三)
-11.12 泛型编程实践(四)
-11.13 泛型编程实践(五)
-11.14 泛型编程实践(六)
-11.15 泛型编程实践(七)
-11.16 泛型编程实践(八)
-11.17 泛型编程实践(九)
-11.18 泛型编程实践(十)
-11.19 编程实践
-第十一讲 泛型编程--编程实践提交入口
-12.1 提纲
-12.2 程序执行环境(一)
-12.3 程序执行环境(二)
-12.4 程序执行环境(三)
-12.5 程序执行环境(四)
-12.6 输入输出(一)
-12.7 输入输出(二)
-12.8 文件系统
-12.9 设备
-12.10 库(一)
-12.11 库(二)
-12.12 makefile文件(一)
-12.13 makefile文件(二)
-12.14 makefile文件(三)
-12.15 编程实践
-第十二讲 Linux系统编程基础--编程实践提交入口
-13.01 提纲
-13.02 进程基本概念
-13.03 信号
-13.04 进程管理(一)
-13.05 进程管理(二)
-13.06 进程管理(三)
-13.07 进程间通信(一)
-13.08 进程间通信(二)
-13.09 进程间通信(三)
-13.10 进程间通信(四)
-13.11 进程池
-13.12 编程实践
-第十三讲 进程编程--编程实践提交入口
-14.1 提纲
-14.2 线程基本概念
-14.3 线程管理(一)
-14.4 线程管理(二)
-14.5 线程管理(三)
-14.6 线程管理(四)
-14.7 线程同步机制(一)
-14.8 线程同步机制(二)
-14.9 C++11线程库(一)
-14.10 C++11线程库(二)
-14.11 C++11线程库(三)
-14.12 C++11线程库(四)
-14.13 C++11线程库(五)
-14.14 编程实践
-第十四讲 线程编程--编程实践提交入口
-15.1 提纲
-15.2 Internet网络协议
-15.3 套接字(一)
-15.4 套接字(二)
-15.5 编程实践
-第十五讲 网络编程--编程实践提交入口