现代 C++ 实战(15):现代设计模式
GoF 23 种设计模式在教科书上往往是一页页继承树。现代 C++ 里很多模式可以大幅瘦身:std::function 替代策略接口、make_unique 管工厂、variant + visit 做访问者——少写类,多写意图。
这一篇对应 demo:ref/cpp_demo/basics/design_patterns/(Strategy、Observer、Builder、Singleton、Command、Visitor)。
这是「现代 C++ 实战」系列的第 15 篇。建议先读 第 14 篇:线程池与背压控制。
一、模式还要学吗?
| 传统痛点 | 现代 C++ 的解 |
|---|---|
| 每种策略一个子类 | std::function + lambda |
| Observer 抽象接口 + 虚函数 | 回调列表;对象观察者用 weak_ptr |
| 复杂构造参数爆炸 | Builder 链式 + 移动语义 |
new/delete 散落 |
make_unique 工厂 + RAII |
| 为每种类型写 Visitor 子类 | std::variant + std::visit(C++17) |
模式解决的是反复出现的结构问题;语言特性升级后,实现方式变,意图不变。
二、Strategy:算法可互换
意图:运行时切换算法,避免 if/else 或 switch 堆叠。
传统:抽象 Strategy 接口 + AddStrategy / MultiplyStrategy…
现代:
1 | class Calculator { |
| 何时仍用继承 | 何时用 std::function |
|---|---|
| 策略有复杂状态、多方法 | 单一可调用对象就够 |
| 需要编译期多态(Concepts) | 运行时切换、配置驱动 |
三、Observer:发布-订阅
意图:主题状态变化时通知多个订阅者,彼此松耦合。
简单场景(demo EventPublisher):
1 | class EventPublisher { |
对象型观察者时,用 weak_ptr 避免循环引用(Subject 持 Observer,Observer 又持 Subject):
1 | class Subject { |
demo 用 lambda 注册三个「观察者」,模拟 MVC 里模型通知多个视图。
四、Builder:链式构建复杂对象
意图:分步设置可选参数,最后一次性构造不可变或半不可变对象。
demo 以 HttpRequest 为例:
1 | HttpRequest req = HttpRequest::builder() |
要点:
- 每个
setXxx返回Builder&(或*this),支持链式 build()调用私有构造函数,保证对象合法- 大字段用 移动 进目标对象,避免多余拷贝
适合:HTTP 请求、SQL 连接串、游戏 Character 配置等参数多的类型。
五、Factory:make_unique + 注册表
意图:创建逻辑集中,调用方依赖抽象而非具体类。
1 | class Widget { public: virtual ~Widget() = default; virtual void draw() = 0; }; |
永远 make_unique,所有权清晰;插件式扩展时注册表可从配置文件或静态初始化填充。
六、RAII 与 Scope Guard
意图:资源获取即初始化,作用域结束自动释放——C++ 的默认模式,不是 GoF 23 之一,但贯穿所有模式。
1 | { |
Scope Guard(C++11 起常用写法):在作用域退出时执行任意清理:
1 | auto guard = finally([] { log("leave scope"); }); |
C++23 有 std::scope_exit;之前可用 small helper 或第三方 scope_guard。与 第 04 篇 的智能指针、lock_guard 一脉相承。
七、demo 中的更多模式
| 模式 | demo 要点 |
|---|---|
| Singleton | Meyer 单例:static ConfigManager instance;,C++11 保证静态局部初始化线程安全 |
| Command | std::function<void()> 队列,支持宏命令(组合多条命令) |
| Visitor | std::variant<Circle, Rectangle, Triangle> + std::visit 算面积 |
Visitor 现代写法:
1 | using Shape = std::variant<Circle, Rectangle, Triangle>; |
新增操作 = 新 visit 函数,不必改 Shape 类层次(类型集合需在设计期固定)。
八、模板 vs 虚函数:怎么选?
| 编译期(模板 / Concepts) | 运行时(虚函数 / std::function) | |
|---|---|---|
| 性能 | 可内联、零开销抽象 | 虚表或 type erasure 有开销 |
| 灵活性 | 类型须在编译期可知 | 配置、插件、脚本驱动 |
| 二进制 | 每实例化一份代码 | 单份 vtable |
| 典型 | 容器算法、Ranges | UI 回调、策略切换 |
原则:热路径、类型集固定 → 模板;需要运行时替换、跨 DLL 边界 → 虚函数或 std::function。
九、demo 运行
1 | cd ref/cpp_demo/basics/design_patterns |
依次输出 Strategy、Observer、Builder、Singleton、Command、Visitor 六段演示。
十、小结
| 模式 | 现代 C++ 抓手 |
|---|---|
| Strategy | std::function、lambda |
| Observer | 回调 vector;对象观察者 + weak_ptr |
| Builder | 链式 Builder& + build() |
| Factory | make_unique + 注册表 |
| RAII | 智能指针、lock_guard、scope guard |
| Visitor | variant + visit |
现代 C++ 实战系列第 15 篇完。下一篇 GoogleTest 单元测试——没有测试的 C++ 项目等于裸奔。
系列导航
| 篇号 | 标题 | 状态 |
|---|---|---|
| 14 | 线程池与背压控制 | ✅ |
| 15 | 现代设计模式(本篇) | ✅ |
| 16 | GoogleTest 单元测试 | 下一篇 |
完整大纲见工作区 docs/CPP_SERIES_OUTLINE.md。











