概述
行为型模式关注对象之间的通信方式——谁调用谁、数据怎么流动、责任怎么分配。这一部分的 9 个组件,每个都把一种行为模式封装成可以直接 #include 就用的头文件。
核心思路: 不是教模式本身,而是展示如何用 C++ 现代特性(std::variant、CRTP、Policy-Based Design、类型擦除)把这些模式变成零侵入的组件。
1. Command 组件 — 可撤销命令框架
意图与痛点
你有一个编辑器、一个操作队列、或者任何需要"撤销/重做"的场景。传统做法是给每个操作写一对 do() 和 undo(),散落在各处,难以管理和序列化。
实现结构
graph TD
COM["<b>Command</b><br/>+execute() void<br/>+undo() void<br/>+description() string"]
COM["<b>CommandHistory</b><br/>-stack~Command*~ doneStack<br/>-stack~Command*~ undoneStack<br/>+execute(cmd) void<br/>+undo() void<br/>+redo() void<br/>+clear() void"]
MAC["<b>MacroCommand</b><br/>-vector~Command*~ commands<br/>+add(cmd) void<br/>+execute() void<br/>+undo() void"]
COM -.-> MAC|-.-|
MAC -.-> COM|-.-|
COM --> COM
核心代码
// command.hpp
#pragma once
#include <memory>
#include <stack>
#include <string>
#include <vector>
namespace design_patterns::command {
// 命令基类 — 只需要实现三个方法
class Command {
public:
virtual ~Command() = default;
virtual void execute() = 0;
virtual void undo() = 0;
virtual std::string description() const { return "unnamed command"; }
};
// 命令历史 — 栈式 undo/redo
class CommandHistory {
public:
void execute(std::unique_ptr<Command> cmd) {
cmd->execute();
doneStack_.push(std::move(cmd));
// 新命令到来,清空 redo 栈
while (!undoneStack_.empty()) undoneStack_.pop();
}
bool canUndo() const { return !doneStack_.empty(); }
bool canRedo() const { return !undoneStack_.empty(); }
void undo() {
if (!canUndo()) return;
auto cmd = std::move(doneStack_.top());
doneStack_.pop();
cmd->undo();
undoneStack_.push(std::move(cmd));
}
void redo() {
if (!canRedo()) return;
auto cmd = std::move(undoneStack_.top());
undoneStack_.pop();
cmd->execute();
doneStack_.push(std::move(cmd));
}
void clear() {
while (!doneStack_.empty()) doneStack_.pop();
while (!undoneStack_.empty()) undoneStack_.pop();
}
private:
std::stack<std::unique_ptr<Command>> doneStack_;
std::stack<std::unique_ptr<Command>> undoneStack_;
};
// 宏命令 — 把多个命令打包成一个
class MacroCommand : public Command {
public:
void add(std::unique_ptr<Command> cmd) {
commands_.push_back(std::move(cmd));
}
void execute() override {
for (auto& cmd : commands_) cmd->execute();
}
void undo() override {
// 反向撤销
for (auto it = commands_.rbegin(); it != commands_.rend(); ++it)
(*it)->undo();
}
private:
std::vector<std::unique_ptr<Command>> commands_;
};
} // namespace design_patterns::command
即插即用
#include "command.hpp"
using namespace design_patterns::command;
// 只需继承 Command,实现 execute() 和 undo()
class AppendTextCmd : public Command {
std::string& target_;
std::string text_;
public:
AppendTextCmd(std::string& t, std::string text) : target_(t), text_(std::move(text)) {}
void execute() override { target_ += text_; }
void undo() override { target_.resize(target_.size() - text_.size()); }
};
使用示例
CommandHistory history;
std::string document;
history.execute(std::make_unique<AppendTextCmd>(document, "Hello "));
history.execute(std::make_unique<AppendTextCmd>(document, "World!"));
history.undo(); // document == "Hello "
history.redo(); // document == "Hello World!"
进阶扩展
- 命令序列化: 给
Command加serialize()/deserialize(),存到文件实现持久化 - 事务命令:
execute()失败时自动回滚 - 异步命令:
execute()返回std::future<void>
常见陷阱
| 陷阱 | 解决 |
|---|---|
| undo 时引用失效 | 用 shared_ptr 共享数据,或存储值拷贝 |
| 宏命令中某个子命令失败 | 用事务模式,全部成功或全部回滚 |
| 命令对象膨胀 | 用 lambda 替代小命令:std::function<void()> |
2. Strategy 组件 — Policy-Based Design
意图与痛点
同一个操作有多种算法实现——排序可以用快排/归并/堆排,日志可以写文件/发网络/打控制台。传统写法是 if-else 或虚函数,运行时选择有开销且不灵活。
实现结构
graph TD
CON["<b>Context</b><br/>+operate() void"]
FAS["<b>FastStrategy</b><br/>+algorithm() void"]
ACC["<b>AccurateStrategy</b><br/>+algorithm() void"]
LOG["<b>LoggingStrategy</b><br/>+algorithm() void"]
CON --> FAS|"模板参数"|
FAS --> CON|"模板参数"|
CON --> ACC|"编译期绑定"|
ACC --> CON|"编译期绑定"|
CON --> LOG|"零开销"|
LOG --> CON|"零开销"|
核心代码
// strategy.hpp
#pragma once
#include <type_traits>
namespace design_patterns::strategy {
// 策略 1:默认排序
struct StdSort {
template <typename T>
static void sort(std::vector<T>& v) {
std::sort(v.begin(), v.end());
}
};
// 策略 2:稳定排序
struct StableSort {
template <typename T>
static void sort(std::vector<T>& v) {
std::stable_sort(v.begin(), v.end());
}
};
// 策略 3:并行排序 (C++17)
struct ParallelSort {
template <typename T>
static void sort(std::vector<T>& v) {
std::sort(std::execution::par, v.begin(), v.end());
}
};
// Context:编译期绑定策略
template <typename SortPolicy = StdSort>
class Sorter {
public:
template <typename T>
void sort(std::vector<T>& data) {
SortPolicy::sort(data);
}
};
// 运行时策略选择(类型擦除版本,有额外开销,但灵活)
class RuntimeSorter {
public:
using SortFunc = std::function<void(std::vector<int>&)>;
explicit RuntimeSorter(SortFunc f) : sort_(std::move(f)) {}
void sort(std::vector<int>& v) { sort_(v); }
private:
SortFunc sort_;
};
} // namespace design_patterns::strategy
即插即用
#include "strategy.hpp"
using namespace design_patterns::strategy;
// 编译期选择 — 零开销,但编译后不可换
Sorter<StableSort> sorter;
sorter.sort(myData);
// 运行时选择 — 有 function 开销,但灵活
RuntimeSorter sorter([](auto& v) { std::ranges::sort(v); });
sorter.sort(myData);
使用示例
// 写一个日志系统,策略控制输出目标
struct FileLogger {
static void log(std::string_view msg) { /* 写文件 */ }
};
struct ConsoleLogger {
static void log(std::string_view msg) { std::cout << msg; }
};
template <typename LoggerPolicy = ConsoleLogger>
class App {
public:
void run() {
LoggerPolicy::log("App started\n");
// ...
}
};
// 开发时用控制台,发布时用文件 —— 改一行模板参数即可
App<FileLogger> prod;
常见陷阱
| 陷阱 | 解决 |
|---|---|
| 策略对象有状态 | 用实例而非静态方法;模板参数存策略对象而非类型 |
| 策略数量爆炸 | 组合优于排列:每个维度一个策略模板参数 |
| 编译期策略无法运行时切换 | 提供类型擦除版作为补充 |
3. Template Method 组件 — CRTP 静态多态
意图与痛点
你想定义一个算法骨架,让子类填充细节步骤。经典面向对象的 virtual + 继承方案有虚函数调用开销,且子类可能忘记调用基类的钩子。
实现结构
graph TD
ALG["<b>AlgorithmBase</b><br/>+run() void<br/>#step1() void<br/>#step2() void<br/>#step3() void<br/>-hook() void"]
CON["<b>ConcreteAlgo</b><br/>+step1() void<br/>+step3() void"]
ALG --> CON|CRTP|
CON --> ALG|CRTP|
核心代码
// template_method.hpp
#pragma once
namespace design_patterns::template_method {
// CRTP 基类:定义骨架,子类通过静态多态填充
template <typename Derived>
class DataProcessor {
public:
// 骨架方法 — 算法流程固定
void process() {
static_cast<Derived*>(this)->validate();
static_cast<Derived*>(this)->transform();
static_cast<Derived*>(this)->save();
}
// 默认钩子:子类可覆盖
void validate() {
// 默认不做额外验证
}
protected:
// 编译期保证子类必须实现这些
~DataProcessor() = default;
};
// 具体实现
class CsvProcessor : public DataProcessor<CsvProcessor> {
public:
void validate() {
// CSV 特有验证
}
void transform() {
// 解析 CSV
}
void save() {
// 写到数据库
}
};
class JsonProcessor : public DataProcessor<JsonProcessor> {
public:
void transform() {
// 解析 JSON
}
void save() {
// 写回文件
}
};
// 骨架工厂函数
template <typename Processor>
void runPipeline(Processor& p) {
p.process();
}
} // namespace design_patterns::template_method
即插即用
#include "template_method.hpp"
using namespace design_patterns::template_method;
CsvProcessor csv;
csv.process(); // 编译期分发,零虚函数开销
JsonProcessor json;
runPipeline(json); // 泛型算法骨架
进阶:编译期钩子检查
// 用 concept (C++20) 检查子类是否实现了必要方法
template <typename T>
concept Processor = requires(T t) {
{ t.transform() } -> std::same_as<void>;
{ t.save() } -> std::same_as<void>;
};
template <Processor Derived>
class CheckedProcessor {
// ...
};
常见陷阱
| 陷阱 | 解决 |
|---|---|
| CRTP 不能统一存到容器 | 加一个类型擦除的包装层 |
| 子类忘记实现某步骤 | C++20 concept 做编译期检查;或基类默认抛异常 |
| 骨架流程变复杂 | 拆成多个小骨架,组合而非继承 |
4. State 组件 — std::variant 状态机
意图与痛点
对象的行为随内部状态改变。传统方案是 enum + switch-case,状态一多就变成意大利面条,添加新状态要改所有 switch。
实现结构
stateDiagram-v2
[*] --> Idle
Idle --> Loading : start()
Loading --> Running : data_ready()
Loading --> Error : timeout()
Running --> Paused : pause()
Paused --> Running : resume()
Running --> Idle : stop()
Error --> Idle : reset()
核心代码
// state.hpp
#pragma once
#include <variant>
#include <string>
namespace design_patterns::state {
// 定义状态类型
struct Idle {};
struct Loading { int progress = 0; };
struct Running { std::string data; };
struct Paused { std::string snapshot; };
struct Error { std::string message; };
// 状态机:std::variant 为核心
class TaskStateMachine {
public:
using State = std::variant<Idle, Loading, Running, Paused, Error>;
// 状态转移 — 访问者模式自动分发
void start() {
std::visit([this](auto& s) { handleStart(s); }, state_);
}
void dataReady(std::string data) {
std::visit([this, &data](auto& s) { handleDataReady(s, data); }, state_);
}
void pause() {
std::visit([this](auto& s) { handlePause(s); }, state_);
}
void resume() {
std::visit([this](auto& s) { handleResume(s); }, state_);
}
void stop() {
std::visit([this](auto& s) { handleStop(s); }, state_);
}
void timeout() {
std::visit([this](auto& s) { handleTimeout(s); }, state_);
}
void reset() {
std::visit([this](auto& s) { handleReset(s); }, state_);
}
const State& state() const { return state_; }
private:
State state_ = Idle{};
// 每个状态-事件组合的处理
void handleStart(Idle&) { state_ = Loading{0}; }
void handleStart(auto&) { /* 忽略:不在 Idle 状态无法启动 */ }
void handleDataReady(Loading& l, std::string& d) {
state_ = Running{std::move(d)};
}
void handleDataReady(auto&, std::string&) {}
void handleTimeout(Loading&) { state_ = Error{"加载超时"}; }
void handleTimeout(auto&) {}
void handlePause(Running& r) { state_ = Paused{r.data}; }
void handlePause(auto&) {}
void handleResume(Paused& p) { state_ = Running{p.snapshot}; }
void handleResume(auto&) {}
void handleStop(Running&) { state_ = Idle{}; }
void handleStop(Paused&) { state_ = Idle{}; }
void handleStop(auto&) {}
void handleReset(Error&) { state_ = Idle{}; }
void handleReset(auto&) {}
};
} // namespace design_patterns::state
即插即用
#include "state.hpp"
using namespace design_patterns::state;
TaskStateMachine fsm;
fsm.start(); // Idle → Loading
fsm.dataReady("数据来了"); // Loading → Running
fsm.pause(); // Running → Paused
fsm.resume(); // Paused → Running
fsm.stop(); // Running → Idle
进阶:状态转移表
// 编译期状态转移矩阵
template <typename From, typename Event>
struct Transition { using To = void; }; // 默认:非法转移
template <> struct Transition<Idle, struct StartEvent> { using To = Loading; };
template <> struct Transition<Loading, struct DataEvent> { using To = Running; };
// ... 转移表集中管理,新状态只需加特化
常见陷阱
| 陷阱 | 解决 |
|---|---|
| variant 状态太多编译慢 | 超过 10 个状态考虑分层状态机 |
| 状态转移遗漏导致数据丢失 | std::visit 的 auto& 默认分支记日志 |
| 状态带数据导致 variant 体积大 | 大数据成员用 shared_ptr 包装 |
5. Chain of Responsibility 组件 — 拦截器管道
意图与痛点
一条请求需要经过多层处理:HTTP 中间件(认证→日志→限流→实际处理)、事件处理链(校验→转换→存储→通知)。传统写法是层层嵌套调用,添加新处理步骤需要修改调用链代码。
实现结构
graph TD
HAN["<b>Handler</b><br/>+handle(req) optional~Response~<br/>+setNext(h) void"]
AUT["<b>AuthHandler</b><br/>+handle(req) optional~Response~"]
LOG["<b>LogHandler</b><br/>+handle(req) optional~Response~"]
RAT["<b>RateLimitHandler</b><br/>+handle(req) optional~Response~"]
COR["<b>CoreHandler</b><br/>+handle(req) optional~Response~"]
HAN -.-> AUT|-.-|
AUT -.-> HAN|-.-|
HAN -.-> LOG|-.-|
LOG -.-> HAN|-.-|
HAN -.-> RAT|-.-|
RAT -.-> HAN|-.-|
HAN -.-> COR|-.-|
COR -.-> HAN|-.-|
HAN --> AUT|next|
AUT --> HAN|next|
LOG --> HAN|next|
HAN --> LOG|next|
RAT --> HAN|next|
HAN --> RAT|next|
COR --> HAN|next|
核心代码
// chain_of_responsibility.hpp
#pragma once
#include <memory>
#include <optional>
namespace design_patterns::chain {
template <typename Request, typename Response = void>
class Handler {
public:
virtual ~Handler() = default;
void setNext(std::unique_ptr<Handler> next) {
next_ = std::move(next);
}
std::optional<Response> handle(const Request& req) {
// 先自己处理
auto result = process(req);
if (result.has_value()) {
return result; // 自己处理完了就返回
}
// 自己不处理,交给下一个
if (next_) {
return next_->handle(req);
}
return std::nullopt; // 链尾,无人处理
}
protected:
virtual std::optional<Response> process(const Request& req) = 0;
private:
std::unique_ptr<Handler> next_;
};
// --- 管道式变体:每个处理器都执行(不中断) ---
template <typename Context>
class Pipeline {
public:
using Step = std::function<void(Context&)>;
Pipeline& addStep(Step step) {
steps_.push_back(std::move(step));
return *this;
}
void execute(Context& ctx) {
for (auto& step : steps_) step(ctx);
}
private:
std::vector<Step> steps_;
};
} // namespace design_patterns::chain
即插即用
#include "chain_of_responsibility.hpp"
using namespace design_patterns::chain;
// 示例:HTTP 请求处理链
struct HttpRequest { std::string token, path; };
class AuthChecker : public Handler<HttpRequest, std::string> {
protected:
std::optional<std::string> process(const HttpRequest& req) override {
if (req.token.empty()) return std::string("401 Unauthorized");
return std::nullopt; // 通过
}
};
// 管道构建
auto auth = std::make_unique<AuthChecker>();
auto log = std::make_unique<LogHandler>();
auth->setNext(std::move(log));
auto resp = auth->handle(request);
使用示例
// 管道式:中间件全执行
Pipeline<HttpRequest> pipeline;
pipeline.addStep([](auto& req) { req.path = sanitize(req.path); })
.addStep([](auto& req) { logRequest(req); })
.addStep([](auto& req) { checkRate(req); });
pipeline.execute(req);
常见陷阱
| 陷阱 | 解决 |
|---|---|
| 链过长性能差 | 考虑使用数组 + 索引代替链表 |
| 某处理器忘了传下去 | 统一基类管理传递逻辑(如上例) |
| 管道式需要中断 | 让 Step 返回 bool(继续/停止) |
6. Mediator 组件 — 类型安全事件总线
意图与痛点
多个组件互相通信——UI 按钮点击→数据模块更新→日志模块记录→网络模块发送。直接耦合会让代码变成蜘蛛网。需要一个中介者统一管理消息。
实现结构
graph TD
EVE["<b>EventBus</b><br/>-map~type_index, vector~handler~~ handlers<br/>+emit~Event~(e) void<br/>+on~Event~(handler) Connection"]
CON["<b>Connection</b><br/>+disconnect() void"]
BUT["<b>Button</b><br/>+click() void"]
DAT["<b>DataModel</b><br/>+onClick(e) void"]
LOG["<b>Logger</b><br/>+onClick(e) void"]
EVE --> CON
CON --> EVE
EVE --> BUT|emit(ClickEvent)|
BUT --> EVE|emit(ClickEvent)|
EVE --> DAT|notify|
DAT --> EVE|notify|
EVE --> LOG|notify|
LOG --> EVE|notify|
核心代码
// mediator.hpp
#pragma once
#include <functional>
#include <memory>
#include <typeindex>
#include <unordered_map>
#include <vector>
namespace design_patterns::mediator {
// RAII 连接句柄
class Connection {
public:
using DisconnectFn = std::function<void()>;
explicit Connection(DisconnectFn fn) : disconnect_(std::move(fn)) {}
~Connection() { disconnect_(); }
Connection(const Connection&) = delete;
Connection& operator=(const Connection&) = delete;
Connection(Connection&&) = default;
Connection& operator=(Connection&&) = default;
void disconnect() { disconnect_(); }
private:
DisconnectFn disconnect_;
};
// 类型安全事件总线
class EventBus {
public:
template <typename Event>
using Handler = std::function<void(const Event&)>;
// 注册监听
template <typename Event>
Connection on(Handler<Event> handler) {
auto& handlers = getHandlers<Event>();
handlers.push_back(std::move(handler));
auto idx = handlers.size() - 1;
return Connection([this, idx]() {
auto& h = getHandlers<Event>();
if (idx < h.size()) h[idx] = nullptr; // 惰性删除
});
}
// 发送事件
template <typename Event>
void emit(const Event& event) {
for (auto& h : getHandlers<Event>()) {
if (h) h(event);
}
// 清理已断开的 handler
auto& handlers = getHandlers<Event>();
handlers.erase(
std::remove(handlers.begin(), handlers.end(), nullptr),
handlers.end());
}
private:
using HandlerList = std::vector<std::function<void(const void*)>>;
template <typename Event>
std::vector<Handler<Event>>& getHandlers() {
auto key = std::type_index(typeid(Event));
if (!storage_.contains(key)) {
storage_[key] = std::vector<Handler<Event>>{};
}
return *reinterpret_cast<std::vector<Handler<Event>>*>(&storage_[key]);
}
std::unordered_map<std::type_index, HandlerList> storage_;
};
} // namespace design_patterns::mediator
即插即用
#include "mediator.hpp"
using namespace design_patterns::mediator;
// 定义事件 — 可以是任意 struct
struct ButtonClick { int x, y; };
struct DataChanged { std::string field, value; };
EventBus bus;
// 模块 A:发送
bus.emit(ButtonClick{10, 20});
// 模块 B:接收
auto conn = bus.on<ButtonClick>([](const auto& e) {
std::cout << "Clicked at " << e.x << ", " << e.y;
});
使用示例
// 模拟 UI + 数据 + 日志的解耦
EventBus bus;
// 日志模块
auto c1 = bus.on<ButtonClick>([](auto&) { log("button clicked"); });
auto c2 = bus.on<DataChanged>([](auto& e) { log("data: " + e.field); });
// 数据模块
auto c3 = bus.on<ButtonClick>([&](auto&) { model->save(); });
// UI 模块只管发
button.onClick = [&]() { bus.emit(ButtonClick{100, 50}); };
进阶
- 优先级队列: 给 handler 加权,按优先级调用
- 异步事件: emit 投递到线程池
- 事件溯源: 记录所有事件,支持回放
常见陷阱
| 陷阱 | 解决 |
|---|---|
| handler 内修改 handler 列表 | 用副本遍历;或标记删除 |
| 事件类型太多编译慢 | 用基类 Event + dynamic_cast(仅必要时) |
| 循环事件(A 发 B → B 发 A) | 加递归深度限制 |
7. Memento 组件 — 状态快照与回滚
意图与痛点
你需要保存一个对象的内部状态,以便以后恢复——文档的 Ctrl+Z、游戏的存档、配置的历史版本。如果直接暴露对象内部结构,封装就被破坏了。
实现结构
graph TD
ORI["<b>Originator</b><br/>-state string<br/>+save() Memento<br/>+restore(m) void<br/>+setState(s) void<br/>+getState() string"]
MEM["<b>Memento</b><br/>-state string<br/>-timestamp time_point<br/>+state() string<br/>+timestamp() time_point"]
CAR["<b>Caretaker</b><br/>-history vector~Memento~<br/>+push(m) void<br/>+pop() Memento<br/>+undo() void<br/>+redo() void"]
ORI --> MEM|creates|
MEM --> ORI|creates|
MEM --> CAR|stores|
CAR --> MEM|stores|
核心代码
// memento.hpp
#pragma once
#include <chrono>
#include <string>
#include <vector>
namespace design_patterns::memento {
// 备忘录:不可变快照
class Memento {
public:
using Timestamp = std::chrono::system_clock::time_point;
Memento(std::string state, Timestamp ts = std::chrono::system_clock::now())
: state_(std::move(state)), timestamp_(ts) {}
const std::string& state() const { return state_; }
Timestamp timestamp() const { return timestamp_; }
private:
std::string state_;
Timestamp timestamp_;
};
// 原始对象:自己负责保存和恢复
class Originator {
public:
void setState(std::string s) { state_ = std::move(s); }
const std::string& state() const { return state_; }
Memento save() const { return Memento(state_); }
void restore(const Memento& m) { state_ = m.state(); }
private:
std::string state_;
};
// 管理者:持有历史记录
class Caretaker {
public:
void push(Memento m) {
history_.push_back(std::move(m));
// 新快照到来,清空 redo 记录
redoIndex_ = history_.size();
}
bool canUndo() const { return history_.size() > 1 && redoIndex_ > 0; }
bool canRedo() const { return redoIndex_ < history_.size(); }
std::optional<Memento> undo() {
if (!canUndo()) return std::nullopt;
redoIndex_--;
return history_[redoIndex_];
}
std::optional<Memento> redo() {
if (!canRedo()) return std::nullopt;
return history_[redoIndex_++];
}
size_t size() const { return history_.size(); }
private:
std::vector<Memento> history_;
size_t redoIndex_ = 0;
};
} // namespace design_patterns::memento
即插即用
#include "memento.hpp"
using namespace design_patterns::memento;
Originator doc;
Caretaker history;
doc.setState("初稿");
history.push(doc.save());
doc.setState("第二版");
history.push(doc.save());
doc.setState("第三版");
// 回退
if (auto m = history.undo()) { doc.restore(*m); } // 回到第二版
if (auto m = history.undo()) { doc.restore(*m); } // 回到初稿
if (auto m = history.redo()) { doc.restore(*m); } // 回到第二版
进阶:泛型快照
// 任意可序列化类型的快照管理器
template <typename T>
requires std::copyable<T>
class SnapshotManager {
public:
void checkpoint(const T& obj) {
snapshots_.push_back(obj);
index_ = snapshots_.size();
}
std::optional<T> undo() { /* ... */ }
std::optional<T> redo() { /* ... */ }
private:
std::vector<T> snapshots_;
size_t index_ = 0;
};
常见陷阱
| 陷阱 | 解决 |
|---|---|
| 快照太大(完整拷贝) | 增量快照:只存变化部分 |
| 快照持有裸指针 | 序列化为值类型或 ID 引用 |
| 快照频率过高 | 合并策略:N 秒内多次修改只存一个 |
8. Iterator 组件 — 泛型迭代器适配
意图与痛点
你要遍历一棵树、一个自定义容器、或者一个流式数据源。每次写 for 循环都要记住内部结构,换一种遍历方式要写完全不同的代码。STL 的迭代器协议提供了统一的访问接口。
实现结构
graph TD
RAN["<b>RangeAdaptor</b><br/>+begin() Iterator<br/>+end() Iterator<br/>+filter(pred) FilterView<br/>+transform(fn) TransformView"]
ITE["<b>Iterator</b><br/>+operator++() Iterator&<br/>+operator*() T&<br/>+operator!=() bool"]
TRE["<b>TreeNode</b><br/>+value T<br/>+children vector~Node~"]
TRE["<b>TreeIterator</b><br/>-stack~Node*~ nodes<br/>+operator++()<br/>+operator*()"]
RAN --> ITE
ITE --> RAN
ITE --> TRE|traverses|
TRE --> ITE|traverses|
核心代码
// iterator.hpp
#pragma once
#include <iterator>
#include <stack>
#include <vector>
namespace design_patterns::iterator {
// 树节点
template <typename T>
struct TreeNode {
T value;
std::vector<TreeNode<T>> children;
explicit TreeNode(T v) : value(std::move(v)) {}
};
// 深度优先迭代器
template <typename T>
class TreeIterator {
public:
using iterator_category = std::forward_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T*;
using reference = T&;
explicit TreeIterator(TreeNode<T>* root = nullptr) {
if (root) stack_.push(root);
}
reference operator*() const { return stack_.top()->value; }
pointer operator->() const { return &stack_.top()->value; }
TreeIterator& operator++() {
auto* node = stack_.top();
stack_.pop();
// 反向入栈,保持从左到右
for (auto it = node->children.rbegin(); it != node->children.rend(); ++it) {
stack_.push(&(*it));
}
return *this;
}
bool operator==(const TreeIterator& other) const {
return stack_.empty() == other.stack_.empty();
}
bool operator!=(const TreeIterator& other) const { return !(*this == other); }
private:
std::stack<TreeNode<T>*> stack_;
};
// 树的可迭代包装
template <typename T>
class IterableTree {
public:
explicit IterableTree(TreeNode<T>& root) : root_(&root) {}
TreeIterator<T> begin() { return TreeIterator<T>(root_); }
TreeIterator<T> end() { return TreeIterator<T>(); }
private:
TreeNode<T>* root_;
};
} // namespace design_patterns::iterator
即插即用
#include "iterator.hpp"
using namespace design_patterns::iterator;
auto root = TreeNode<std::string>("root");
root.children.push_back(TreeNode<std::string>("A"));
root.children.push_back(TreeNode<std::string>("B"));
root.children[0].children.push_back(TreeNode<std::string>("A1"));
// 现在树支持 range-for!
for (auto& value : IterableTree(root)) {
std::cout << value << "\n"; // root → A → A1 → B
}
进阶:视图适配器
template <typename Iter>
class FilterView {
Iter begin_, end_;
std::function<bool(const typename Iter::value_type&)> pred_;
public:
// 用 STL filter_iterator 模式实现
// C++20 直接用 std::views::filter
};
常见陷阱
| 陷阱 | 解决 |
|---|---|
| 迭代器失效(修改容器) | 文档标注;或用 COW |
| 树很深时栈溢出 | 改用堆上的显式栈(非递归) |
| 自定义迭代器与 STL 不兼容 | 确保定义全部 5 个 typedef |
9. Visitor 组件 — std::variant + std::visit
意图与痛点
你需要对一个类层次结构中的每个具体类型执行不同操作——比如 AST 遍历、序列化、语义分析。经典的虚函数 Visitor 模式需要给每个类加 accept(),侵入性强。
C++17 引入的 std::variant + std::visit 提供了更轻量的替代方案。
实现结构
graph TD
AST["<b>ASTNode</b><br/>+Number<br/>+Plus<br/>+Multiply"]
EVA["<b>Evaluator</b><br/>+operator()(Number) double<br/>+operator()(Plus) double<br/>+operator()(Multiply) double"]
PRI["<b>Printer</b><br/>+operator()(Number) string<br/>+operator()(Plus) string<br/>+operator()(Multiply) string"]
AST --> EVA|std::visit|
EVA --> AST|std::visit|
AST --> PRI|std::visit|
PRI --> AST|std::visit|
核心代码
// visitor.hpp
#pragma once
#include <variant>
#include <string>
#include <memory>
namespace design_patterns::visitor {
// 定义 AST 节点类型(替代继承体系)
struct Number { double value; };
struct Plus { std::unique_ptr<Number> left, right; };
struct Multiply { std::unique_ptr<Number> left, right; };
using ASTNode = std::variant<Number, Plus, Multiply>;
// Visitor 1:求值
struct Evaluator {
double operator()(const Number& n) const {
return n.value;
}
double operator()(const Plus& p) const {
return std::visit(Evaluator{}, ASTNode{*p.left})
+ std::visit(Evaluator{}, ASTNode{*p.right});
}
double operator()(const Multiply& m) const {
return std::visit(Evaluator{}, ASTNode{*m.left})
* std::visit(Evaluator{}, ASTNode{*m.right});
}
};
// Visitor 2:打印
struct Printer {
std::string operator()(const Number& n) const {
return std::to_string(n.value);
}
std::string operator()(const Plus& p) const {
return "(" + std::visit(Printer{}, ASTNode{*p.left})
+ " + "
+ std::visit(Printer{}, ASTNode{*p.right}) + ")";
}
std::string operator()(const Multiply& m) const {
return "(" + std::visit(Printer{}, ASTNode{*m.left})
+ " * "
+ std::visit(Printer{}, ASTNode{*m.right}) + ")";
}
};
// 工具函数:一行调用
template <typename Visitor>
auto visit(const ASTNode& node, Visitor&& v) {
return std::visit(std::forward<Visitor>(v), node);
}
} // namespace design_patterns::visitor
即插即用
#include "visitor.hpp"
using namespace design_patterns::visitor;
// 构建 AST: (3 + 5) * 2
auto left = std::make_unique<Number>(3.0);
auto right = std::make_unique<Number>(5.0);
auto plus = Plus{std::move(left), std::move(right)};
auto ast = ASTNode{std::move(plus)};
// 求值
double result = visit(ast, Evaluator{}); // 8.0
// 打印
std::string expr = visit(ast, Printer{}); // "(3.0 + 5.0)"
进阶:泛型 Visitor 工具
// 过载模式 — 用 lambda 组合 visitor
template <typename... Ts>
struct overload : Ts... { using Ts::operator()...; };
// 无需定义结构体,直接 lambda
auto visitor = overload{
[](const Number& n) { return n.value; },
[](const Plus& p) { return visit(ASTNode{*p.left}, visitor)
+ visit(ASTNode{*p.right}, visitor); },
[](const Multiply& m){ return visit(ASTNode{*m.left}, visitor)
* visit(ASTNode{*m.right}, visitor); },
};
double result = visit(ast, visitor);
常见陷阱
| 陷阱 | 解决 |
|---|---|
| variant 递归定义需要指针 | 用 unique_ptr 或 shared_ptr 包装子节点 |
| std::visit 必须穷举所有类型 | 用 overload + auto 默认分支 |
| 新增类型需要改所有 visitor | variant 加新类型时编译报错,强制更新 |
组件总览
graph TB
subgraph "行为型组件工具箱"
CMD[Command<br/>可撤销命令]
STG[Strategy<br/>策略替换]
TMP[Template Method<br/>骨架复用]
STA[State<br/>状态机]
CHN[Chain<br/>拦截器管道]
MED[Mediator<br/>事件总线]
MEM[Memento<br/>快照回滚]
ITR[Iterator<br/>泛型遍历]
VIS[Visitor<br/>variant 分发]
end
CMD --> |配合| MEM
MED --> |配合| CMD
CHN --> |配合| STG
STA --> |配合| VIS
总结
9 个行为型组件,核心工程的共同点:
- 零侵入: 每个都是单一头文件,
#include即用 - 类型安全: 模板 + concept + variant 在编译期拦住误用
- 现代 C++: 不写
new/delete,RAII 管理所有资源(Connection 句柄、命令栈、快照历史) - 可组合: Command + Memento 实现编辑器 undo,Mediator + Chain 实现中间件系统
下一篇预告: 并发组件扩展 —— Active Object、Monitor Object、Thread Pool 的 C++ 封装。
本文属于「C++ 组件架构实战」课程系列,所有代码 MIT 协议开源。