第1章 MFC框架的理解
1.1 动态创建类
(1)背景------传统方式的局限
传统 C++ 创建对象必须 #include 头文件、手动 new,类型在编译期写死。团队协作时,每新增一个类,调用方代码就要跟着改,耦合严重。
(2)框架设计------自实现 一套运行时类型识别(RTTI)
C++ 内置 RTTI 只能做类型判断,无法按字符串创建对象。该框架在此基础上扩展,通过四个核心组件实现了完整的运行时类型识别与动态创建能力:
- 顶级基类
LObject:所有类的公共祖先,提供isKindOf、getRuntimeClass等统一接口 CRuntimeClass结构体:每个类对应一个实例,存储类名、对象大小、schema、工厂函数指针、父类指针,是 RTTI 的元信息载体- 全局单向链表 :以
m_pFirstClass为链表头,将所有注册类的元信息串联起来,是运行时查找的基础 - 两个宏 :
DECLARE_DYNAMIC负责在.h里声明元信息结构体、虚函数、工厂函数;IMPLEMENT_DYNAMIC负责在.cpp里填充元信息、生成静态注册器,程序启动时自动将本类插入全局链表
(3)带来的价值
框架将每个类变成自给自足的独立单元 ------继承 LObject、挂上两个宏,类就完成了自我注册,与其他任何类没有编译期依赖。团队协作时各写各的,互不干扰:
总节:
- 传统方式:调用方自己写
new,必须包含头文件,类型编译期写死。 - 该框架:通过两个宏 + 全局单向链表 + 顶级基类
LObject。 - 每个类继承
LObject,通过两个宏将自身元信息(类名、大小、schema、工厂函数指针、父类指针)在程序启动时自动注册进全局链表。 - 使用时通过字符串查链表找到对应节点,再调用节点里存的工厂函数指针,由框架内部的
new完成创建,调用方无需包含头文件,也无需感知具体类型。
该框架是对 C++ 内置 RTTI 的超集实现------用两个宏 + 全局单向链表 + 顶级基类,在编译期埋下元信息,运行期实现按字符串动态创建对象,解决了内置 RTTI 做不到的运行时对象工厂问题,同时从根本上消除了团队协作中的编译期耦合。
1.2 定时器
借鉴LibUV源码,基于最小堆(完全二叉树) + 就绪队列的定时器实现。
(1)初始化阶段
- 获取当前系统时间戳 → 存入
loop->time; - 初始化空的最小堆
timer_heap; - 初始化空的就绪队列
timer_ready; - 初始化
timer_counter = 0;
(2)启动定时器阶段
timer_start(&timer_a, task_a, 3000, 2000);
- 计算到期时间戳:
timeout = loop->time + 3000 - 分配启动序号:
start_id = timer_counter++ - 插入最小堆(按
timeout排序,timeout相同时按start_id排序)
启动多个定时器后,堆自动排好序:
堆:13000, 15000, 18000, 20000 ← 堆顶最早到期
(3)事件循环阶段(每次调用 timer_run())
1、更新时间;
loop->time = now_ms(); // 获取最新系统时间戳
- 从最小堆堆顶取出到期的定时器,放置到等待处理的等待队列中。再次判断堆顶是否还有到期时间的定时器。有的话再次取出放置到等待队列中。
cpp
// 阶段1:从堆摘到队列
while (堆顶到期) {
handle = 摘堆顶();
放入就绪队列; // 此时还没执行回调,也还没放回堆
}
// 阶段2:从队列取出执行
while (队列不为空) {
handle = 取出队首();
handle->cb(handle); // 先执行回调
if (handle->repeat > 0) {
// 回调执行完了,才重新放回堆
handle->timeout = loop->time + handle->repeat;
插入堆中;
}
}
-
队列执行完回调,重新放回最小堆中。
-
依次循环。
1.3 信号与槽
C++ Signal/Slot Library (sigslot) - Browse Files at SourceForge.net
sigslot的原理其实非常简单,它就是一个变化的观察者模式。观察者模式如下所示:

观察者模式,首先让 Observer("观察者")对象 注册到 Subject("被观察者") 对象中。当 Subject 状态发生变化时,遍历所有注册到自己的 Observer 对象,并调用它们的 notify方法。
sigslot与观察者模式类似,它使用signal("信号")和slot("槽"),区别在于 signal 主动连接自己感兴趣的类及其方法,将它们保存到自己的列表中。当发射信号时,它遍历所有的连接,调用 slot("槽") 方法。