现代 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…
现代(demo 的 Calculator):
1 | class Calculator { |
demo 为了直观按值拷贝 strategy;生产代码可以写成 strategy_(std::move(strategy))、构造函数加 explicit,省一次 std::function 拷贝并避免隐式转换。
| 何时仍用继承 | 何时用 std::function |
|---|---|
| 策略有复杂状态、多方法 | 单一可调用对象就够 |
| 需要编译期多态(Concepts) | 运行时切换、配置驱动 |
三、Observer:发布-订阅
意图:主题状态变化时通知多个订阅者,彼此松耦合。
简单场景(demo EventPublisher):
1 | class EventPublisher { |
对象型观察者时,用 weak_ptr 避免循环引用(Subject 持 Observer,Observer 又持 Subject)。demo 没有这一版,下面是示意:
1 | class Observer { |
demo 用 lambda 注册三个「观察者」(分别收到、处理、记录事件),再依次发布 "用户登录"、"数据更新" 两个事件——相当于 MVC 里模型通知多个视图。
四、Builder:链式构建复杂对象
意图:分步设置可选参数,最后一次性构造不可变或半不可变对象。
demo 以 HttpRequest 为例(Builder 是嵌套类 HttpRequest::Builder,HttpRequest::builder() 返回一个新的 Builder):
1 | HttpRequest request = HttpRequest::builder() |
要点:
- 每个
setXxx/addHeader返回Builder&(return *this;),支持链式 build()调用HttpRequest的私有构造函数,外部只能经由 Builder 创建对象- demo 的 setter 都是
const std::string&拷贝;字段大时可改成按值接收再 移动 进目标对象,避免多余拷贝
适合:HTTP 请求、SQL 连接串、游戏 Character 配置等参数多的类型。
五、Factory:make_unique + 注册表
意图:创建逻辑集中,调用方依赖抽象而非具体类。demo 没有 Factory 的演示,下面是示意代码:
1 | class Widget { public: virtual ~Widget() = default; virtual void draw() = 0; }; |
永远 make_unique,所有权清晰;插件式扩展时注册表可从配置文件或静态初始化填充。
六、RAII 与 Scope Guard
意图:资源获取即初始化,作用域结束自动释放——C++ 的默认模式,不是 GoF 23 之一,但贯穿所有模式。
1 | { |
Scope Guard(C++11 起常用写法):在作用域退出时执行任意清理。标准库没有现成的,自己写一个小 helper 即可(C++17):
1 | template <class F> |
注意 scope_exit 并没有进入 C++23 标准,它只在 Library Fundamentals TS v3 里以 std::experimental::scope_exit 出现;实际项目常用 GSL 的 gsl::finally 或上面这种自写 helper。与 第 04 篇 的智能指针、lock_guard 一脉相承。
七、demo 中的更多模式
| 模式 | demo 要点 |
|---|---|
| Singleton | Meyer 单例:static ConfigManager instance;,C++11 保证静态局部初始化线程安全 |
| Command | CommandInvoker 用 std::vector<std::function<void()>> 存命令,lambda 捕获 Light / Fan 引用;一个 lambda 里调用多个操作即宏命令 |
| Visitor | std::variant<Circle, Rectangle, Triangle> + std::visit:calculateArea 算面积、printInfo 打印信息 |
Visitor 现代写法(demo 的 calculateArea):
1 | using Shape = std::variant< |
新增操作 = 新 visit 函数(demo 的 printInfo 就是第二个操作),不必改 Shape 类层次(类型集合需在设计期固定)。
八、模板 vs 虚函数:怎么选?
| 编译期(模板 / Concepts) | 运行时(虚函数 / std::function) | |
|---|---|---|
| 性能 | 可内联、零开销抽象 | 虚表或 type erasure 有开销 |
| 灵活性 | 类型须在编译期可知 | 配置、插件、脚本驱动 |
| 二进制 | 每实例化一份代码 | 单份 vtable |
| 典型 | 容器算法、Ranges | UI 回调、策略切换 |
原则:热路径、类型集固定 → 模板;需要运行时替换、跨 DLL 边界 → 虚函数或 std::function。
九、demo 运行
1 | cd ref/cpp_demo/basics/design_patterns |
单文件 design_patterns.cpp、单一 target design_patterns(C++17),main() 依次调用 demo_strategy_pattern() … demo_visitor_pattern(),输出 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 单元测试 | ✅ |











