《Modern C++ Design》读书笔记
基本信息
- 书名: Modern C++ Design: Generic Programming and Design Patterns Applied
- 作者: Andrei Alexandrescu
- 页数: 285页
- 文件大小: 1.92MB
- 分类: C++ / 设计模式 / 泛型编程
核心主题
这本书开创性地展示了如何将设计模式与泛型编程结合,创造出可复用的”泛型组件”(Generic Components)。书中的Loki库提供了工业级的C++泛型组件实现。
核心理念:泛型组件
泛型组件是可用于多种场景的设计模板,编译器会展开生成具体代码。它们:
- 减少样板代码
- 更好地表达设计意图
- 支持最小化重编码的设计结构复用
第一部分:技术基础
第1章 Policy-Based Class Design(策略类设计)
核心问题:如何实现高度可定制的类,同时保持接口简单?
解决方案:策略模式 + 模板元编程
关键概念:
- 策略类(Policy Class):封装特定行为或功能
- 策略组合:通过模板参数组合多个策略
- 默认策略:让类在没有显式策略时也能工作
- 不完全实例化:某些策略可以”缺失”,提供可选功能
示例:
template <
typename ThreadingModel = SingleThreaded,
typename SmallObjAlloc = DefaultSmallObjAlloc
>
class MyClass : public ThreadingModel,
protected SmallObjAlloc {
// 使用策略提供的功能
};关键洞察:
- 策略类之间应该相互独立(无依赖)
- 每个策略只关注一个关注点(单一职责)
- 策略可以定义可选功能的接口
第2章 Techniques(编译期技术)
本章介绍泛型编程的基础工具:
2.1 编译期断言
- 用模板特化在编译时检查条件
- 比
assert()更早发现问题
2.2 部分模板特化
- 为特定类型组合提供专门实现
- 是实现类型选择的基础
2.3 局部类(Local Classes)
- 定义在函数内部的类
- 只能作为模板参数传递
2.4 整数常量到类型的映射
- 用枚举+模板特化实现编译期查表
- 用于类型索引和静态映射
2.5 Type-to-Type Mapping
- 核心模式:
type2type<SomeType> - 用于在编译期传递类型信息
- 解决函数模板不能传递类型的问题
2.6 Type Selection
select2:二分选择类型select3:三分选择类型- 配合
is_convertible等 trait 使用
2.7 编译期类型检测
is_convertible:检测类型转换关系inheritance_checker:检测继承关系is_base_of:基类检测
2.8 NullType 和 EmptyType
NullType:空类型列表的结束标志EmptyType:空值类型,占位用- Typelist的基本组成部分
2.9 Type Traits
is_void、is_pointer、is_integral等- 查询类型的属性
- 配合 SFINAE 实现重载解析
第3章 Typelists(类型列表)
核心问题:如何在编译期处理任意数量的类型?
解决方案:递归链表式的类型列表
typedef TypeList<int, TypeList<char, NullType> > MyList;关键操作:
Length<Typelist>:计算类型数量IndexType<Typelist, N>:获取第N个类型Append<Typelist, Type>:追加类型Find<Typelist, Type>:查找类型Eraser<Typelist, Type>:删除类型ReplaceType<Typelist, Old, New>:替换类型
应用:
- 多方法分派
- 策略组合
- 类型安全的变长参数
第二部分:泛型组件
第4章 小对象分配器
问题:频繁创建/销毁小对象导致内存碎片和性能开销
解决方案:预分配固定大小块,按需分配小对象
FixedAllocator<N>:固定大小 N 的分配器SmallObjAllocator:通用小对象分配器(支持多种大小)
关键设计:
- 分配块(chunk)包含固定大小的插槽
- 使用空闲列表管理已释放的内存
- 线程安全版本使用锁或原子操作
第5章 泛型函数对象
问题:如何让任意函数/函数对象以统一接口调用?
解决方案:Functor 模板 + 转发机制
Functor 支持:
- 普通函数指针
- 成员函数指针
- 函数对象(operator())
- lambda 表达式(现代C++)
Functor<int(int, int)> f = someFunction;
int result = f(3, 4);应用:
- 命令模式泛型化
- Undo/Redo 支持
- 回调注册
第6章 单例实现
问题:经典单例的线程安全、延迟初始化、生命周期管理问题
解决方案:SingletonHolder 模式
template <typename T>
class SingletonHolder {
static T* instance_;
public:
static T& GetInstance() {
if (!instance_) {
instance_ = new T;
}
return *instance_;
}
};关键改进:
- Phoenix Singleton:解决”静默死亡”问题(程序启动顺序问题)
- Longevity Singleton:控制单例的生命周期
- 线程安全版本
第7章 智能指针
核心类型:
SmartPtr<T>:引用计数智能指针- 支持所有权转移
- 线程安全版本
- 数组支持
关键设计:
- 引用计数对象单独分配
- 自定义删除器
- 隐式转换为原始指针
第8章 对象工厂
问题:如何在不暴露构造细节的情况下创建对象?
解决方案:Factory 泛型组件
// 注册类型
Factory::Register<Base, Derived>("key");
// 创建对象
Base* obj = Factory::Create("key");扩展:Clone 工厂支持基于原型的创建
第9章 抽象工厂
问题:如何创建相关对象族而不指定具体类?
解决方案:泛型 AbstractFactory 组件
结合 Prototype 和 Factory 模式
第10章 Visitor 模式
泛型 Visitor:支持任意数量的参数类型
Acyclic Visitor:
- 消除循环依赖
- 每个类实现不同的 Visit 接口
- 使用动态_cast 查找支持的方法
第11章 多方法
问题:C++只支持单分发,如何实现多分发?
解决方案:Double Dispatcher 模式
- StaticDispatcher:编译期静态分派
- BasicDispatcher:运行时对数时间分派
- BasicFastDispatcher:运行时常数时间分派
应用:图形变换、碰撞检测等
附录:多线程支持
Loki 提供最小的多线程抽象:
ThreadingModel 策略:
SingleThreaded:无同步ObjectLevelLockable:对象级锁(每对象一个互斥)ClassLevelLockable:类级锁(每类一个互斥)
关键接口:
class MyClass : public ObjectLevelLockable<MyClass> {
Lock lock(*this); // RAII 锁
// ... 线程安全操作
};原子操作:
AtomicAdd、AtomicSubtract等- 针对平台优化的整数原子操作
关键概念总结
| 概念 | 说明 | 应用 |
|---|---|---|
| Policy-Based Design | 通过模板参数定制行为 | 核心设计哲学 |
| Typelist | 编译期类型链表 | 类型元编程基础 |
| Functors | 统一函数调用接口 | 命令模式、回调 |
| SingletonHolder | 线程安全单例 | 全局状态管理 |
| SmartPtr | 引用计数指针 | 资源管理 |
| Factory | 对象创建封装 | 解耦创建与使用 |
| Double Dispatcher | 多方法分派 | 泛型Visitor、多态 |
| ThreadingModel | 多线程策略 | 线程安全组件 |
实践要点
-
策略设计原则:
- 每个策略独立关注一个方面
- 策略之间不应有依赖
- 提供合理的默认策略
-
编译期编程:
- 能编译期完成的工作不要放运行时
- 类型安全优于运行时检查
- 使用SFINAE和trait进行重载解析
-
Loki库使用:
- 可作为独立组件使用
- 也可作为学习泛型模式的参考
- 注意:Loki是C++98时代的产物,现代C++有更好的替代品
与其他知识的关联
- 与本笔记中 Cpp-Coding-Standards-101条编码规范 互补
- Policy设计思想影响后来的 Effective-Modern-C++-Scott-Meyers
- Loki的概念在Boost库中大量应用
- 与现代C++的Concepts、Modules有理念上的传承
行动点
- 研究Loki库的GitHub仓库(如果还存在)
- 理解Policy-Based Design在现代C++中的应用
- 思考如何将Typelist思想应用到当前项目
- 对比现代C++(C++11/14/17/20)中的替代方案