CppCon 2018 学习:EFFECTIVE REPLACEMENT OF DYNAMIC POLYMORPHISM WITH std::variant
“Effective Replacement of Dynamic Polymorphism with std::variant
” 是现代 C++ 中一个重要的设计思路:用静态类型机制(如 std::variant
)来替代传统的虚函数(动态多态),从而实现 更安全、更高效、更易维护的多态行为。
下面我来详细讲解这个设计理念、适用场景、优缺点、和对比。
一、传统的动态多态(virtual)
struct Animal {virtual void speak() const = 0;virtual ~Animal() = default;
};
struct Dog : Animal {void speak() const override { std::cout << "Woof\n"; }
};
struct Cat : Animal {void speak() const override { std::cout << "Meow\n"; }
};
void make_sound(const Animal& a) {a.speak(); // 运行时绑定
}
- 使用虚函数表(vtable)+ 指针/引用
- 多态是运行时决定的
- 灵活,但有以下缺点:
- 需要堆分配(new)
- 无法进行
switch
分支匹配 - 性能有虚调用开销
- 类型不透明,不利于优化和序列化
二、替代方案:用 std::variant
代替虚函数
#include <variant>
#include <iostream>
struct Dog {void speak() const { std::cout << "Woof\n"; }
};
struct Cat {void speak() const { std::cout << "Meow\n"; }
};
using Animal = std::variant<Dog, Cat>;
void make_sound(const Animal& a) {std::visit([](const auto& animal) {animal.speak(); // 编译期就确定}, a);
}
特点
- 没有 vtable → 不需要虚函数、继承、多态指针
- 所有类型都在编译期已知
- 用
std::visit
调用对应的行为(称为“静态多态”) - 结构更扁平、可组合性更好
- 性能更优:无虚调用、无堆分配
三、适用场景对比
场景 | 用 virtual (动态多态) | 用 std::variant (静态多态) |
---|---|---|
类型不固定、来自外部插件 | ||
类型组合有限、已知 | ||
高性能(如游戏 ECS) | ||
面向开放层次结构(Open/Closed) | ||
模型具有生命周期多态行为(需要共享接口) | 需额外设计 | |
模型简单、可枚举类型 | 非常适合 |
四、关键点总结
方面 | virtual | std::variant |
---|---|---|
多态类型 | 可扩展 | 固定集合 |
类型识别 | RTTI / dynamic_cast | 编译期类型 (std::visit ) |
性能 | 虚函数调度 | 编译期分发,高效 |
所需语法 | 基类 + 指针 | std::variant + lambda |
类型安全 | 低(运行期错误) | 高(编译期检查) |
举例:状态机、消息、几何图元等典型适合 variant
场景
struct Circle { double r; };
struct Square { double side; };
struct Triangle { double base, height; };
using Shape = std::variant<Circle, Square, Triangle>;
double area(const Shape& shape) {return std::visit(overload{[](const Circle& c) { return 3.14 * c.r * c.r; },[](const Square& s) { return s.side * s.side; },[](const Triangle& t) { return 0.5 * t.base * t.height; }}, shape);
}
这里你完全不需要 virtual
、基类、指针,且:
- 所有 case 都由编译器检查
- 新加类型必须修改
variant
类型 → 类型封闭(Closed set) - 高效,无虚调度开销
总结一句话:
如果你的多态对象集合是封闭的(固定的几种类型),并且你需要类型安全、高性能、无堆分配的代码 —— 那么使用
std::variant
替代virtual
是更“Modern C++”的做法。
这个例子展示的是 传统的动态多态(dynamic polymorphism) 使用方式。我们逐步来解释其工作机制,以及如何能被现代 C++ 特性(如 std::variant
)替代。
原始代码解释
class base : noncopyable {
public:virtual ~base() = default;virtual void foo() = 0;
};
class x : public base {
public:void foo() override { std::cout << "x\n"; }
};
class y : public base {
public:void foo() override { std::cout << "y\n"; }
};
std::unique_ptr<base> b = std::make_unique<x>();
b->foo(); // 输出:x
解释:
base
是一个抽象基类,带有纯虚函数foo()
。x
和y
是派生类,实现了foo()
。b
是一个std::unique_ptr<base>
,指向派生类对象x
。b->foo()
是通过虚函数表(vtable)实现的运行时多态。
什么是动态多态?
- 基类指针(或引用)指向派生类对象
- 调用虚函数时,由 运行时 决定调用哪一个版本
- 通过 vtable 和 vptr 实现
- 典型用途:插件系统、框架扩展点、接口调用
动态多态的缺点
- 必须通过指针/引用调用(不能值语义)
- 会失去具体类型信息(type erasure)
- 不能在
switch
等结构中分支处理 - 虚函数调用有轻微的性能开销
- 无法序列化多态对象(类型信息不透明)
使用 std::variant
替代动态多态
当你知道有哪些派生类,推荐使用 std::variant
来代替:
#include <variant>
#include <iostream>
struct x {void foo() { std::cout << "x\n"; }
};
struct y {void foo() { std::cout << "y\n"; }
};
using variant_base = std::variant<x, y>;
void call_foo(variant_base& v) {std::visit([](auto& obj) {obj.foo();}, v);
}
int main() {variant_base v = x{};call_foo(v); // 输出:xv = y{};call_foo(v); // 输出:y
}
动态多态 vs std::variant
对比
特性 | virtual 方式 | std::variant 方式 |
---|---|---|
运行时分发 | (编译期分发) | |
类型开放性 | 可添加子类 | 类型固定 |
性能 | 稍慢(虚调用) | 更快(静态分发) |
类型安全 | 低(容易用错) | 高(编译器检查) |
使用方式 | 指针 / 引用 | 值类型 |
支持访问成员 | 不方便(需 RTTI) | std::visit 统一访问 |
总结:如何选择?
- 如果你需要 扩展性、运行时加载插件、动态行为 → 用
virtual
(动态多态) - 如果你有 已知的固定类型集合,想要 高性能、类型安全 → 用
std::variant
(静态多态)
进阶思考:怎么改写你给的代码为 std::variant
?
你原始代码:
std::unique_ptr<base> b = std::make_unique<x>();
b->foo();
用现代方式替换为:
std::variant<x, y> b = x{};
std::visit([](auto& obj) { obj.foo(); }, b);
即可获得同样的行为,而无需虚函数。
如何用 std::variant
有效替代动态多态(dynamic polymorphism)
原始动态多态方式(使用虚函数)
class base {
public:virtual ~base() = default;virtual void foo() = 0;
};
class x : public base {
public:void foo() override { std::cout << "x\n"; }
};
class y : public base {
public:void foo() override { std::cout << "y\n"; }
};
std::unique_ptr<base> b = std::make_unique<x>();
b->foo(); // 输出 x
特点:
- 运行时决定调用哪个
foo()
(虚函数 + vtable) - 必须通过指针/引用访问
- 只能使用继承层级下的类(
base
的派生类)
替代方式:使用 std::variant
+ std::visit
#include <variant>
#include <iostream>
struct x {void foo() { std::cout << "x\n"; }
};
struct y {void foo() { std::cout << "y\n"; }
};
int main() {std::variant<x, y> b = x{};std::visit([](auto&& v) { v.foo(); }, b); // 输出 xb = y{};std::visit([](auto&& v) { v.foo(); }, b); // 输出 y
}
为什么 std::variant
是动态多态的“有效替代”?
特性 | 虚函数 (base* ) | std::variant |
---|---|---|
运行时多态 | (通过 visit() ) | |
编译时类型检查 | ||
值语义 | (指针) | (直接存储对象) |
速度 | 较慢(虚函数调用) | 更快(inline 展开) |
类无继承关系 | (duck typing) | |
内存布局稳定 | (heap 分配) | (固定大小,无 heap) |
关键点解析
1. duck typing(鸭子类型)
- 不需要共同的基类
- 只要有
foo()
方法,就可以放进std::variant
struct apple { void foo(); };
struct orange { void foo(); };
// apple 和 orange 没有继承关系,但 std::variant 可以接受
2. 更快、更短
- 没有虚函数调用的间接跳转
- 编译器可优化
std::visit()
(如内联) - 对象直接嵌入在
variant
里,无需new
3. 适用范围
适合:
- 所有“行为一致”的类
- 固定的类型集合
- 性能敏感、无需运行时扩展
不适合: - 插件系统(运行时扩展)
- 不确定的类型集合
- 必须用接口隐藏细节时
例子总结(对比)
动态多态方式:
std::unique_ptr<base> b = std::make_unique<x>();
b->foo();
std::variant
方式:
std::variant<x, y> b = x{};
std::visit([](auto&& v){ v.foo(); }, b);
总结一句话:
当你已知类型集合,且只需要它们有相同行为(比如
foo()
),
那么
std::variant
+std::visit
是比传统继承更 现代、更安全、更高效 的选择。
我们来一步一步深入理解什么是 有限状态机(FSM, Finite State Machine)。
基本定义(你已经说得对了)
A Finite State Machine 是一种抽象机器,
它在任何时刻都只处于有限数量的状态之一。
用一句话理解 FSM:
“一个系统根据输入,在状态之间切换,
并在每个状态中做出不同的行为。”
构成要素(FSM 的五个核心部分):
- States(状态):机器可以处于的不同情境,例如:
Idle
,Playing
,Paused
。 - Events / Inputs(事件/输入):触发状态变化的动作,例如:
PlayPressed
,PausePressed
。 - Transitions(转移):某个输入触发从一个状态跳转到另一个状态。
- Initial state(初始状态):启动时的状态。
- Actions(动作):进入状态时或转换时执行的逻辑代码。
现实例子:音频播放器
一个简单音乐播放器的状态机:
- 状态(States):
Stopped
Playing
Paused
- 事件(Events):
PlayPressed
PausePressed
StopPressed
- 状态转换表(Transition Table):
| Current State | Input | Next State |
| ------------- | ------------ | ---------- |
| Stopped | PlayPressed | Playing |
| Playing | PausePressed | Paused |
| Playing | StopPressed | Stopped |
| Paused | PlayPressed | Playing |
| Paused | StopPressed | Stopped |
状态图(State Diagram)
[Stopped]|PlayPressed↓[Playing] <--> [Paused]↑ ↓ ↑ ↓StopPressed Play Stop↓[Stopped]
C++中实现一个 FSM(简化版)
#include <iostream>
#include <string>
enum class State { Stopped, Playing, Paused };
void handle_event(State& state, const std::string& event) {if (state == State::Stopped && event == "play") {state = State::Playing;std::cout << "Now Playing\n";} else if (state == State::Playing && event == "pause") {state = State::Paused;std::cout << "Paused\n";} else if (state == State::Playing && event == "stop") {state = State::Stopped;std::cout << "Stopped\n";} else if (state == State::Paused && event == "play") {state = State::Playing;std::cout << "Resuming\n";} else if (state == State::Paused && event == "stop") {state = State::Stopped;std::cout << "Stopped\n";} else {std::cout << "Invalid action in current state\n";}
}
int main() {State current = State::Stopped;handle_event(current, "play"); // Now Playinghandle_event(current, "pause"); // Pausedhandle_event(current, "play"); // Resuminghandle_event(current, "stop"); // Stopped
}
FSM 在工程中的常见用途:
场景 | 示例 |
---|---|
UI 逻辑 | 按钮状态(hovered, pressed, disabled) |
游戏 | 玩家状态(走、跳、攻击) |
网络协议 | TCP 状态(SYN_SENT, ESTABLISHED, etc.) |
工业控制 | 机器状态(加热中、冷却中、完成) |
总结一句话:
有限状态机就是一个状态 + 事件驱动的行为模型,
可以让复杂系统的逻辑更清晰、更可控、更可维护。
mermaid:
stateDiagram-v2[*] --> state_idlestate_idle --> state_connecting : connectstate_connecting --> state_connected : connectedstate_connecting --> state_idle : timeout [n = n_max]state_connected --> state_connecting : disconnectstate_connecting --> state_connecting : timeout [n < n_max]
提供的是一个 FSM(有限状态机) 的状态图(用 Mermaid 的 stateDiagram-v2
语法),并附有定义性描述。我们来一步步分析并理解这张图和对应的概念。
首先,什么是 FSM?
你的总结已经非常准确:
“状态机在响应外部输入(称为事件)时改变状态,这种状态的转换称为 transition。FSM 包括状态集合、初始状态、每个 transition 的条件。”
分析你的 FSM 状态图
各个组成部分解释:
状态(States):
state_idle
:空闲状态state_connecting
:正在连接state_connected
:已连接
初始状态(Initial State):
[ * ] --> state_idle
表示 FSM 启动时的状态是state_idle
事件驱动的状态转换(Transitions):
From | To | Trigger/Event (with condition) |
---|---|---|
[*] | state_idle | 初始状态 |
state_idle | state_connecting | connect 事件 |
state_connecting | state_connected | connected 事件(连接成功) |
state_connecting | state_idle | timeout 且 n == n_max |
state_connecting | state_connecting | timeout 且 n < n_max (重试) |
state_connected | state_connecting | disconnect 事件(主动断开) |
条件(Guards):
这些是“是否允许转换”的条件判断:
timeout [n = n_max]
:如果超时并且重试次数到达上限 → 回到idle
timeout [n < n_max]
:如果超时但还有机会 → 继续留在connecting
重试
完整 FSM 特征(你的模型完美覆盖了):
要素 | 说明 |
---|---|
状态(States) | idle , connecting , connected |
初始状态 | idle |
输入事件(Events) | connect , connected , disconnect , timeout |
转换(Transitions) | 条件触发的状态变更 |
动作(Actions) | 可加在 transition 时执行,例如 on timeout: n++ |
对应的 C++ 思路(伪代码):
enum class State { Idle, Connecting, Connected };
int retry_count = 0;
const int max_retries = 3;
State current = State::Idle;
void handle_event(std::string event) {switch (current) {case State::Idle:if (event == "connect") {current = State::Connecting;}break;case State::Connecting:if (event == "connected") {current = State::Connected;} else if (event == "timeout") {if (retry_count >= max_retries) {current = State::Idle;} else {retry_count++;// stay in Connecting}}break;case State::Connected:if (event == "disconnect") {current = State::Connecting;}break;}
}
小结:
你对 FSM 的定义和使用理解非常清晰:
- 有限状态集
- 事件驱动转换
- 状态转移包含条件判断(guard)
- 适用于连接状态、UI 控件、游戏角色、网络协议等场景
CASE #1 SINGLE DYNAMIC DISPATCH
mermaid:
classDiagramclass event {+connect+connected+disconnect+timeout}class fsm {+dispatch(event)}class state {+on_event(event) : 0}class state_idleclass state_connectingclass state_connectedfsm o--> statestate o--> state_idlestate o--> state_connectingstate o--> state_connected
《用 std::variant
替代动态多态(dynamic polymorphism)》的早期示例,使用传统方式实现了一个 单动态派发(Single Dynamic Dispatch) 的有限状态机(FSM)模型。
目标:
构建一个通用 FSM 模板,基于事件 Event
,通过虚函数实现状态转移。
代码+注释+分析
// 声明状态接口,基于事件类型进行参数化
template<typename Event>
class state : noncopyable { // 非拷贝类,继承非拷贝性
public:virtual ~state() = default;// 接收事件,返回新状态(可能是nullptr,表示无状态转换)virtual std::unique_ptr<state> on_event(Event) = 0;
};
分析:
- 使用
virtual
实现 动态派发。 on_event()
是核心:接收到一个Event
,返回新的state
(通过多态切换状态)。- 返回
nullptr
→ 状态不变。
template<typename Event>
class fsm {std::unique_ptr<state<Event>> state_; // 当前状态对象
public:// 构造时设置初始状态explicit fsm(std::unique_ptr<state<Event>> state): state_(std::move(state)) {}// 派发事件,可能引发状态转移void dispatch(Event e) {auto new_state = state_->on_event(e); // 调用虚函数,根据事件生成下一个状态if (new_state) {state_ = std::move(new_state); // 如果有新状态,切换当前状态}// 否则保持原状态}
};
分析:
fsm::dispatch()
是事件驱动的入口。on_event(e)
动态绑定,调用当前状态的处理逻辑。- 若返回新状态指针,则状态切换;否则保持不变。
- 使用
std::unique_ptr
实现资源管理和所有权转移。
示例用途(假设):
我们可以这样构造具体状态:
struct event { int id; };
class idle_state : public state<event> {
public:std::unique_ptr<state<event>> on_event(event e) override {if (e.id == 1) {return std::make_unique<active_state>();}return nullptr;}
};
class active_state : public state<event> {
public:std::unique_ptr<state<event>> on_event(event e) override {if (e.id == 0) {return std::make_unique<idle_state>();}return nullptr;}
};
用法:
fsm<event> machine{std::make_unique<idle_state>()};
machine.dispatch({1}); // 转为 active_state
machine.dispatch({0}); // 回到 idle_state
结构图(简化):
fsm<event>
└── state<Event>├── idle_state└── active_state
每个状态类实现 on_event()
,决定如何切换。
存在的问题:
- 每个状态对象都需要堆分配(
std::unique_ptr
),有性能和生命周期开销。 - 虚函数调用不利于内联,无法做大量编译时优化。
- 不支持多态内联、多事件批处理等优化。
后续改进(CppCon 2018 提议):
用 std::variant<State1, State2, ...>
替代 std::unique_ptr<base>
:
- 静态类型已知
- 避免虚函数
- 内联优化
- 编译期检查状态合法性
小结:
优点 | 缺点 |
---|---|
简洁清晰,结构化强 | 运行时开销较高(虚函数 + 堆分配) |
经典 OOP 方式易于理解和扩展 | 性能瓶颈、类型不安全(无法静态保证合法性) |
这段代码是一个使用动态多态(virtual)实现的有限状态机(FSM),模拟了一个 connection_fsm
(连接状态机)。状态之间通过接收到的事件(如 connect
, connected
, timeout
, disconnect
)进行转换。
整体结构
event ──> on_event(Event) ──> new state(或 nullptr)
状态:
- state_idle
- state_connecting
- state_connected
事件:
- connect
- connected
- timeout
- disconnect
完整代码 + 注释 + 分析
#include <iostream>
#include <memory>
enum class event {connect,connected,timeout,disconnect
};
定义事件类型:event
是 FSM 的输入。
// 抽象状态基类
template<typename Event>
class state {
public:virtual ~state() = default;virtual std::unique_ptr<state> on_event(Event) = 0;
};
这是所有状态的基类。每个状态响应 Event
类型的输入,返回新的状态(或不变,返回 nullptr
)。
// 有限状态机类
template<typename Event>
class fsm {std::unique_ptr<state<Event>> state_;
public:explicit fsm(std::unique_ptr<state<Event>> state): state_(std::move(state)) {}void dispatch(Event e) {auto new_state = state_->on_event(e);if (new_state) {state_ = std::move(new_state);}}
};
fsm::dispatch()
:将事件传入当前状态处理。- 状态返回新的状态(指针),由 FSM 持有并替换旧状态。
using s = state<event>; // 简化写法
state<event>
是我们 FSM 中所有状态的抽象基类,s
是其别名。
具体状态类
class state_idle final : public s {
public:std::unique_ptr<s> on_event(event e) override;
};
class state_connecting final : public s {static constexpr int n_max = 3;int n = 0; // 超时计数器
public:std::unique_ptr<s> on_event(event e) override;
};
class state_connected final : public s {
public:std::unique_ptr<s> on_event(event e) override;
};
state_idle
: 等待连接命令。state_connecting
: 正在尝试连接,允许一定次数重试。state_connected
: 成功建立连接。
事件处理实现(核心逻辑)
state_idle
的响应逻辑
std::unique_ptr<s> state_idle::on_event(event e) {if (e == event::connect) {std::cout << "Transition: idle -> connecting\n";return std::make_unique<state_connecting>();}return nullptr; // 保持 idle
}
- 如果收到
connect
,转入connecting
状态。 - 否则状态不变。
state_connecting
的响应逻辑
std::unique_ptr<s> state_connecting::on_event(event e) {switch (e) {case event::connected:std::cout << "Transition: connecting -> connected\n";return std::make_unique<state_connected>();case event::timeout:++n;std::cout << "Timeout count: " << n << "\n";if (n < n_max) {return nullptr; // 继续等待} else {std::cout << "Max timeouts reached. Back to idle\n";return std::make_unique<state_idle>();}default:return nullptr; // 不响应其他事件}
}
connected
: 转到connected
状态。timeout
: 如果次数少于最大值n_max
,继续等待;否则回到idle
。- 其他事件无效。
state_connected
的响应逻辑
std::unique_ptr<s> state_connected::on_event(event e) {if (e == event::disconnect) {std::cout << "Transition: connected -> idle\n";return std::make_unique<state_idle>();}return nullptr; // 保持连接状态
}
disconnect
: 回到idle
。- 其他事件忽略。
包装为 FSM 类
class connection_fsm : public fsm<event> {
public:connection_fsm(): fsm<event>(std::make_unique<state_idle>()) {}
};
connection_fsm
是用户接口类,初始状态为 idle
。
用法示例(添加主函数)
int main() {connection_fsm conn;conn.dispatch(event::connect); // idle -> connectingconn.dispatch(event::timeout); // retry 1conn.dispatch(event::timeout); // retry 2conn.dispatch(event::timeout); // retry 3, back to idleconn.dispatch(event::connect); // idle -> connectingconn.dispatch(event::connected); // connecting -> connectedconn.dispatch(event::disconnect); // connected -> idle
}
总结
元素 | 功能 |
---|---|
state<T> | 抽象基类,定义事件响应 |
fsm<T> | 状态持有与管理 |
state_idle | 等待连接 |
state_connecting | 连接中,处理超时重试 |
state_connected | 已连接状态,等待断开 |
dispatch() | 将事件发送给当前状态,可能产生状态切换 |
用 std::variant
替代传统动态多态** 的内容。现在我们来深入理解这部分讲述的 Single Dynamic Dispatch(单一动态分发) 特点及测试方式:
测试状态转换(Testing Transitions)
template<typename Fsm, typename... Events>
void dispatch(Fsm& fsm, Events... events) {(fsm.dispatch(events), ...); // C++17 左折叠表达式
}
解释:
这是一个变参模板函数,接受任意数量的 events
并将它们逐个传递给 FSM 的 dispatch()
方法。
(fsm.dispatch(events), ...);
等价于:
fsm.dispatch(event1);
fsm.dispatch(event2);
fsm.dispatch(event3);
// ...
使用 C++17 的fold expression 实现,简洁且强大,避免了手动循环或展开。
示例用法:
connection_fsm fsm;
dispatch(fsm, event::connect, event::timeout, event::connected, event::disconnect);
输出示意(如果你在状态转换中有打印语句):
Transition: idle -> connecting
Timeout count: 1
Transition: connecting -> connected
Transition: connected -> idle
这就是状态转换流程的测试输出。
Single Dynamic Dispatch 的优缺点分析
特性 | 含义 |
---|---|
Open to new alternatives | 用户可以随时扩展新的 state 类型,不需要修改 base 类。例如 state_paused 。 |
Closed to new operations | 不能后期添加新方法(只能扩展 on_event() )。要加其他逻辑,得改基类。 |
Multi-level | 支持多层继承,适合复杂状态继承体系。 |
Object-oriented | 一切围绕类和虚函数运行,符合经典面向对象设计。 |
为何要换成 std::variant
?
因为:
动态多态 virtual | 替代方案 std::variant (静态多态) |
---|---|
运行时开销(虚表,堆分配) | 编译期决策(无虚表,栈上对象) |
状态操作不可扩展(只能写 on_event ) | 用 std::visit 可自由定义操作 |
易出错(忘记实现虚函数时无编译报错) | 静态类型检查更安全 |
代码分散,状态间联系弱 | 所有状态集中定义,清晰一致 |
总结理解
dispatch(fsm, ...)
用来简洁测试多个状态转换路径。- 单一动态分发的实现是面向对象经典做法,但不利于未来操作的扩展。
- 你可以继续使用这种结构,或者尝试将状态机重构为
std::variant
+std::visit
的形式,进一步获得性能和类型安全优势。
以下是将传统的**动态多态 FSM(有限状态机)**实现,完整地重构为使用 std::variant
和 std::visit
的 静态多态版本(无虚函数,无堆分配):
std::variant
版本的 FSM 完整代码
#include <iostream>
#include <variant>
// 定义事件类型
enum class event { connect, connected, timeout, disconnect };
// 1. 前向声明状态类
struct state_idle;
struct state_connecting;
struct state_connected;
// 2. 定义状态的变体类型,代表所有可能的状态之一
using state = std::variant<state_idle, state_connecting, state_connected>;
// 3. 定义各状态结构体,并声明它们的事件处理函数
struct state_idle {// 事件处理函数,接收事件和超时计数,返回新的状态state on_event(event e, int& n) const;
};
struct state_connecting {state on_event(event e, int& n) const;
};
struct state_connected {state on_event(event e, int& n) const;
};
// 4. 实现状态的事件处理函数
// idle状态处理事件
state state_idle::on_event(event e, int&) const {if (e == event::connect) { // 收到 connect 事件,状态转为 connectingstd::cout << "idle -> connecting\n";return state_connecting{};}return *this; // 其他事件状态不变
}
// connecting状态处理事件
state state_connecting::on_event(event e, int& n) const {switch (e) {case event::connected: // 收到 connected 事件,状态转为 connected,重置计数器std::cout << "connecting -> connected\n";n = 0;return state_connected{};case event::timeout: // 收到 timeout 事件,计数器+1,3次超时后回到 idleif (++n >= 3) {std::cout << "connecting -> idle (timeout)\n";n = 0;return state_idle{};}std::cout << "connecting: timeout #" << n << "\n";return *this; // 未达到超时次数,保持当前状态default:return *this; // 其他事件保持状态不变}
}
// connected状态处理事件
state state_connected::on_event(event e, int&) const {if (e == event::disconnect) { // 收到 disconnect 事件,状态转为 idlestd::cout << "connected -> idle\n";return state_idle{};}return *this; // 其他事件状态不变
}
// 状态机定义
struct connection_fsm {state current; // 当前状态,存储为variantint timeout_count = 0; // 超时计数器,连接中状态使用// 派发事件,调用当前状态的 on_event 函数并更新状态void dispatch(event e) {current = std::visit([e, this](auto&& s) -> state { return s.on_event(e, timeout_count); },current);}
};
// 辅助函数:批量派发多个事件,利用 fold expression
template <typename Fsm, typename... Events>
void dispatch(Fsm& fsm, Events... events) {(fsm.dispatch(events), ...);
}
// 主函数测试状态机
int main() {connection_fsm fsm{state_idle{}}; // 初始状态为 idledispatch(fsm,event::connect, // idle -> connectingevent::timeout, // timeout #1event::timeout, // timeout #2event::timeout, // timeout #3 -> idle (超时重置)event::connect, // idle -> connectingevent::connected, // connecting -> connectedevent::disconnect // connected -> idle);
}
输出结果示例:
idle -> connecting
connecting: timeout #1
connecting: timeout #2
connecting -> idle (timeout)
idle -> connecting
connecting -> connected
connected -> idle
优势对比
动态多态实现 | 静态多态(std::variant) |
---|---|
使用 virtual ,运行时决策 | 使用 std::visit ,编译期决策 |
需堆分配 std::unique_ptr | 所有状态栈上分配,零堆开销 |
类型不安全,易忘记 override | 编译器强制 on_event() 存在 |
新状态扩展需写派生类 | 直接添加新 struct 和 case |
dispatch 写法复杂 | std::visit 一行搞定 |
如果你还想要支持 带数据状态(比如连接次数、延迟等),只需在 struct 中添加成员变量即可。 |
讨论了用std::variant替代传统**动态多态(dynamic polymorphism)**的方式,特别是在“事件(event)携带数据”时该怎么设计。
我帮你梳理下核心点和背景,方便你理解:
背景
- 传统动态多态(用基类指针或引用+虚函数)是经典的面向对象设计方法。
- 你有一个基类
event
,然后派生出具体事件,比如event_connect
,带有数据(比如address_
)。 - 状态
state_idle
等通过虚函数on_event(const event&)
处理不同事件。为了区分事件类型,通常会用dynamic_cast
来尝试转换为具体事件类型。
std::unique_ptr<state> state_idle::on_event(const event& e)
{if (auto ptr = dynamic_cast<const event_connect*>(&e)) {// 处理 event_connect} else {// 处理其他事件}
}
这个方案的问题
dynamic_cast
是运行时开销较大的类型判断。- 多个事件派生类和状态类,
on_event
函数变得臃肿、难维护。 - 使用虚函数本质上依赖运行时类型信息(RTTI),违背现代C++中更倾向于静态类型安全和零开销抽象的设计理念。
CppCon 2018 演讲提出的替代方案
- 用
std::variant
代替继承多态,将所有事件和状态都声明成不同类型的variant成员。 - 利用
std::visit
静态分发事件,避免dynamic_cast
。 - 事件携带数据也通过
std::variant
里的具体事件类型存储,访问时类型安全且零开销。
“双重分发(Double Dispatch)”和“访问者模式(Visitor Pattern)”
- 传统 OOP 的多态就是单重分发:函数调用根据单个对象的动态类型选择实现。
- 但是状态机中
on_event(state, event)
函数需要根据两个对象的运行时类型(当前状态 + 事件类型)来分发,这叫做多重分发。 - 传统做法用双重分发(Visitor Pattern)复杂且笨重。
总结
- 动态多态+
dynamic_cast
实现事件处理不优,会有性能和可维护性问题。 - 用
std::variant
结合std::visit
,以静态多态和模式匹配替代传统运行时多态,既安全又高效。 - 这种方式,事件携带数据时也可以灵活处理,不用靠基类接口和强制转换。
双重分发(Double Dispatch)是面向对象编程中的一种技术,用于根据两个对象的运行时类型选择要调用的函数。
单重分发 vs 双重分发
单重分发(Single Dispatch):
这是 C++ 的默认行为:函数调用基于接收者(this指针)的运行时类型。
struct Shape {virtual void draw() const = 0;
};
struct Circle : Shape {void draw() const override { std::cout << "Circle\n"; }
};
Shape* s = new Circle;
s->draw(); // 单重分发:只根据 *s 的类型(Circle)选择函数
但这对两个对象的协作不够:
比如你有两个对象:Shape
和 Renderer
,你想根据两者的具体类型组合决定调用哪个函数,那就需要 双重分发。
双重分发示例:撞击处理(Visitor 模式)
你希望这样写代码:
Shape* a = new Circle;
Shape* b = new Square;
a->collide_with(b); // 要根据 a 和 b 的具体类型组合决定行为
问题:
C++ 只支持单重分发,因此 a->collide_with(b)
只会根据 a
的类型决定调用哪个 collide_with
函数,无法根据 b
的类型再进一步分发。
解决方法:双重分发(典型通过 Visitor 模式实现)
让第一个对象反向调用第二个对象的另一个虚函数:
struct Shape {virtual void collide_with(Shape* other) = 0;virtual void collide_with_circle(class Circle* c) = 0;virtual void collide_with_square(class Square* s) = 0;
};
struct Circle : Shape {void collide_with(Shape* other) override {other->collide_with_circle(this); // 再调用另一个虚函数,第二次分发}void collide_with_circle(Circle*) override { std::cout << "Circle vs Circle\n"; }void collide_with_square(Square*) override { std::cout << "Circle vs Square\n"; }
};
struct Square : Shape {void collide_with(Shape* other) override {other->collide_with_square(this);}void collide_with_circle(Circle*) override { std::cout << "Square vs Circle\n"; }void collide_with_square(Square*) override { std::cout << "Square vs Square\n"; }
};
现在你可以:
Shape* a = new Circle;
Shape* b = new Square;
a->collide_with(b); // 正确分发到 Circle vs Square
为什么叫“双重”?
因为:
- 第一次分发是
a->collide_with(b)
:根据a
的类型调用合适的collide_with
- 第二次分发是
b->collide_with_circle(a)
:根据b
的类型调用合适的重载函数
这就是 双重分发。
用 std::variant
怎么做双重分发?
std::variant<Circle, Square> a, b;
std::visit([](auto&& obj1, auto&& obj2) {handle_collision(obj1, obj2); // 编译期双重分发(静态类型组合)
}, a, b);
相比虚函数方式:
- 不用写复杂的 Visitor 接口
- 编译期检查所有组合是否实现
- 零开销
总结一句话:
双重分发是函数调用同时依赖两个对象的运行时类型,传统通过虚函数+Visitor实现,现代 C++ 更推荐用
std::variant
+std::visit
静态实现,类型安全、可扩展、零开销。
下面是一个使用 std::variant
和 std::visit
实现的“双重分发”完整示例,模拟了两个几何图形对象之间的“碰撞”(collision)逻辑。
使用 std::variant
实现双重分发(推荐方式)
#include <iostream>
#include <variant>
// 1. 定义图形类型
struct Circle {};
struct Square {};
struct Triangle {};
// 2. 定义所有可能的图形类型组合的碰撞行为
void collide(const Circle&, const Circle&) {std::cout << "Circle collides with Circle\n";
}
void collide(const Circle&, const Square&) {std::cout << "Circle collides with Square\n";
}
void collide(const Square&, const Circle&) {std::cout << "Square collides with Circle\n";
}
void collide(const Square&, const Square&) {std::cout << "Square collides with Square\n";
}
void collide(const Circle&, const Triangle&) {std::cout << "Circle collides with Triangle\n";
}
void collide(const Triangle&, const Square&) {std::cout << "Triangle collides with Square\n";
}
// 3. 类型别名
using Shape = std::variant<Circle, Square, Triangle>;
// 4. 双重分发
void collide(const Shape& s1, const Shape& s2) {std::visit([](auto&& a, auto&& b) {collide(a, b); // 静态分发}, s1, s2);
}
// 5. 测试
int main() {Shape a = Circle{};Shape b = Square{};Shape c = Triangle{};collide(a, b); // Circle collides with Squarecollide(b, a); // Square collides with Circlecollide(a, a); // Circle collides with Circlecollide(a, c); // Circle collides with Trianglecollide(c, b); // Triangle collides with Square
}
输出示例
Circle collides with Square
Square collides with Circle
Circle collides with Circle
Circle collides with Triangle
Triangle collides with Square
优点
- 零运行时开销(编译期确定函数)
- 类型安全(漏写组合时会编译错误)
- 可扩展(添加新类型时只需加新函数)
「双重分发(Double Dispatch)」和 Visitor 模式实现的状态机完整代码,并配有详细注释和结构化分析。
双重分发(Visitor Pattern)状态机完整代码与注释
#include <iostream>
#include <memory>
#include <string>
#include <string_view>
// -----------------------------
// 非拷贝基类,用于防止拷贝
// -----------------------------
struct noncopyable {noncopyable() = default;noncopyable(const noncopyable&) = delete;noncopyable& operator=(const noncopyable&) = delete;
};
// 前向声明
class state;
// -----------------------------
// 事件基类,模板参数是状态类型
// 每个具体事件都会继承它
// dispatch:第二次分发(到 state)
// -----------------------------
template<typename State>
struct event : private noncopyable {virtual ~event() = default;virtual std::unique_ptr<State> dispatch(State& s) const = 0;
};
// -----------------------------
// FSM 模板,持有当前状态,派发事件
// -----------------------------
template<typename State, typename Event>
class fsm {std::unique_ptr<State> state_;
public:explicit fsm(std::unique_ptr<State> state): state_(std::move(state)) {}void dispatch(const Event& e) {auto new_state = e.dispatch(*state_);if (new_state)state_ = std::move(new_state);}
};
// -----------------------------
// 所有事件类型(携带或不携带数据)
// -----------------------------
struct event_connect final : public event<state> {std::string_view address_;explicit event_connect(std::string_view addr) : address_(addr) {}std::string_view address() const { return address_; }std::unique_ptr<state> dispatch(state& s) const override;
};
struct event_connected final : public event<state> {std::unique_ptr<state> dispatch(state& s) const override;
};
struct event_disconnect final : public event<state> {std::unique_ptr<state> dispatch(state& s) const override;
};
struct event_timeout final : public event<state> {std::unique_ptr<state> dispatch(state& s) const override;
};
// -----------------------------
// 状态基类,声明对所有事件的响应接口
// 每个派生状态类可重写它需要响应的事件
// -----------------------------
class state : noncopyable {
public:virtual ~state() = default;// 默认处理所有事件为“忽略”virtual std::unique_ptr<state> on_event(const event_connect&) { return nullptr; }virtual std::unique_ptr<state> on_event(const event_connected&) { return nullptr; }virtual std::unique_ptr<state> on_event(const event_disconnect&) { return nullptr; }virtual std::unique_ptr<state> on_event(const event_timeout&) { return nullptr; }
};
// -----------------------------
// 空闲状态
// -----------------------------
class state_idle final : public state {
public:using state::on_event; // 继承其余 on_event 默认实现std::unique_ptr<state> on_event(const event_connect& e) override;
};
// -----------------------------
// 连接中状态
// -----------------------------
class state_connecting final : public state {std::string address_;int timeout_count_ = 0;
public:explicit state_connecting(std::string addr) : address_(std::move(addr)) {}std::unique_ptr<state> on_event(const event_connected&) override;std::unique_ptr<state> on_event(const event_timeout&) override;
};
// -----------------------------
// 已连接状态
// -----------------------------
class state_connected final : public state {
public:std::unique_ptr<state> on_event(const event_disconnect&) override;
};
// -----------------------------
// 各个事件的 dispatch 实现
// 调用状态类中的 on_event,形成第二次分发
// -----------------------------
std::unique_ptr<state> event_connect::dispatch(state& s) const {return s.on_event(*this);
}
std::unique_ptr<state> event_connected::dispatch(state& s) const {return s.on_event(*this);
}
std::unique_ptr<state> event_disconnect::dispatch(state& s) const {return s.on_event(*this);
}
std::unique_ptr<state> event_timeout::dispatch(state& s) const {return s.on_event(*this);
}
// -----------------------------
// 状态行为实现
// -----------------------------
std::unique_ptr<state> state_idle::on_event(const event_connect& e) {std::cout << "Idle -> Connecting to " << e.address() << "\n";return std::make_unique<state_connecting>(std::string(e.address()));
}
std::unique_ptr<state> state_connecting::on_event(const event_connected&) {std::cout << "Connecting -> Connected\n";return std::make_unique<state_connected>();
}
std::unique_ptr<state> state_connecting::on_event(const event_timeout&) {++timeout_count_;if (timeout_count_ >= 3) {std::cout << "Connecting -> Idle (timeout)\n";return std::make_unique<state_idle>();}std::cout << "Connecting: timeout #" << timeout_count_ << "\n";return nullptr;
}
std::unique_ptr<state> state_connected::on_event(const event_disconnect&) {std::cout << "Connected -> Idle\n";return std::make_unique<state_idle>();
}
// -----------------------------
// 主程序:测试 FSM 逻辑
// -----------------------------
int main() {fsm<state, event<state>> machine{std::make_unique<state_idle>()};event_connect connect{"example.com"};event_timeout timeout;event_connected connected;event_disconnect disconnect;machine.dispatch(connect);machine.dispatch(timeout);machine.dispatch(timeout);machine.dispatch(timeout); // should go back to idlemachine.dispatch(connect);machine.dispatch(connected);machine.dispatch(disconnect);
}
核心概念理解
概念 | 说明 |
---|---|
单重分发 | 根据一个对象类型决定调用哪个函数(如虚函数) |
双重分发 | 根据两个对象类型(状态和事件)决定调用哪个函数 |
Visitor 模式 | 将操作和数据结构解耦,事件驱动调用具体状态逻辑 |
dispatch() | 在事件中调用状态的 on_event() :动态分发 |
on_event() | 在状态中接收事件:事件类型重载形成第二层分发 |
状态切换 | 返回新的 std::unique_ptr<state> ,在 FSM 中替换当前状态 |
优点
- 支持 携带数据的事件(如 connect 事件传地址)
- 使用传统面向对象方式,便于理解
- 模块化好,易于扩展新状态/新事件类型
提供的代码展示了 双重分发(Double Dispatch)+ 面向对象 FSM(状态机) 的完整实现。下面我将为你系统地分析和解释每个部分,让你能深刻理解它的结构、原理、运行机制,以及它为何被称为 “双重分发”。
1. 什么是双重分发?
单重分发:
C++ 虚函数的机制是根据对象的动态类型选择函数,例如:
Animal* a = new Dog;
a->speak(); // 调用的是 Dog::speak(),这是一次“单重分发”
双重分发(Double Dispatch):
需要根据两个对象的动态类型来决定调用哪个函数。
在本例中:
- 第一次分发:事件调用状态的
dispatch()
。 - 第二次分发:状态调用具体的
on_event()
来处理事件。
所以我们有两个“动态绑定”的地方 → 双重分发。
2. 架构组成和类关系图
+----------------+| event<State> |<----------------------------------++----------------+ |^ |+----------------------+----------------------------+ || | | |
+----------------+ +---------------------+ +----------------------+
| event_connect | | event_timeout | | event_connected | ...
+----------------+ +---------------------+ +----------------------+| dispatch(State&) |↓+-------------------+| state |+-------------------+^ ^ ^| | |+--------+ | +-------------++----------------+ | +------------------+| state_idle | | | state_connected |+----------------+ | +------------------+|+--------------------+| state_connecting |+--------------------+
fsm<state, event<state>>
3. 核心组件说明
event<State>
(事件基类)
template<typename State>
struct event {virtual std::unique_ptr<State> dispatch(State& s) const = 0;
};
- 所有事件都继承自它。
- 定义了虚函数
dispatch()
,让事件自己去“触发”状态处理。
state
(状态基类)
class state {virtual std::unique_ptr<state> on_event(const event_connect&) { return nullptr; }...
};
- 定义了对所有事件类型的默认处理接口。
- 子类重写这些函数,实现具体逻辑。
- 返回新的状态(
std::unique_ptr<state>
),用以更新 FSM。
fsm<state, event<state>>
template<typename State, typename Event>
class fsm {std::unique_ptr<State> state_;void dispatch(const Event& e) {auto new_state = e.dispatch(*state_);if (new_state)state_ = std::move(new_state);}
};
- 持有当前状态指针。
dispatch()
把事件交给状态处理,通过双重分发来更新状态。
4. 状态转换逻辑详解
以 state_connecting
为例:
std::unique_ptr<state> state_connecting::on_event(const event_timeout&) {++timeout_count_;if (timeout_count_ >= 3) {std::cout << "Connecting -> Idle (timeout)\n";return std::make_unique<state_idle>();}std::cout << "Connecting: timeout #" << timeout_count_ << "\n";return nullptr;
}
- 如果
timeout
连续发生 3 次 → 状态退回 idle。 - 返回
nullptr
表示状态保持不变。
5. 测试代码逻辑
int main() {fsm<state, event<state>> machine{std::make_unique<state_idle>()};// 构造事件event_connect connect{"example.com"};event_timeout timeout;event_connected connected;event_disconnect disconnect;// 事件流machine.dispatch(connect); // idle -> connectingmachine.dispatch(timeout); // connecting: timeout #1machine.dispatch(timeout); // connecting: timeout #2machine.dispatch(timeout); // connecting -> idlemachine.dispatch(connect); // idle -> connectingmachine.dispatch(connected); // connecting -> connectedmachine.dispatch(disconnect); // connected -> idle
}
终端输出:
Idle -> Connecting to example.com
Connecting: timeout #1
Connecting: timeout #2
Connecting -> Idle (timeout)
Idle -> Connecting to example.com
Connecting -> Connected
Connected -> Idle
优势总结
特性 | 说明 |
---|---|
双重分发 | 同时考虑“事件类型”与“状态类型”来决定行为 |
面向对象 | 状态和事件都是对象,封装良好 |
易扩展 | 新增状态或事件,只需新增类并重写相应方法 |
支持携带数据的事件 | 如 event_connect 可以传递 address |
与 std::variant
的对比(可选)
特性 | variant 实现 | OOP 双重分发实现 |
---|---|---|
性能 | 编译期类型选择,无虚表 | 运行时虚函数开销 |
可扩展性(新增事件) | 不易扩展,需要修改 std::visit | 易扩展,新建 event 派生类即可 |
可扩展性(新增状态) | 同样不易扩展 | 易扩展,新建 state 派生类即可 |
数据传递 | 需要 std::variant 携带数据 | 每个事件类型类可以自带数据成员 |
mermaid code
classDiagramclass event {state* dispatch(state&) = 0}class fsm {+dispatch(const event&)}class state {+on_event(const event connect&)+on_event(const event connected&)+on_event(const event disconnect&)+on_event(const event timeout&)}class state_idleclass state_connecting {+std::string address}class state_connectedclass event_connect {+std::string_view address}class event_connectedclass event_disconnectclass event_timeoutstate <|-- state_idlestate <|-- state_connectingstate <|-- state_connectedfsm --> stateevent <|.. fsmevent <|-- event_connectevent <|-- event_disconnectevent <|-- event_timeoutevent <|-- event_connected
下面是你提供的代码中所展示的 CRTP(Curiously Recurring Template Pattern)+ 双重分发 FSM(有限状态机) 的 完整示例代码、注释、分析和理解总结。
CRTP 状态机完整代码(加注释)
#include <iostream>
#include <memory>
#include <string>
#include <string_view>
// -----------------------------
// 工具类:不可拷贝 base
// -----------------------------
struct noncopyable {noncopyable() = default;noncopyable(const noncopyable&) = delete;noncopyable& operator=(const noncopyable&) = delete;
};
// 前向声明
class state;
// -----------------------------
// 1. 抽象事件接口:带虚函数 dispatch
// -----------------------------
template<typename State>
struct event : private noncopyable {virtual ~event() = default;virtual std::unique_ptr<State> dispatch(State& s) const = 0;
};
// -----------------------------
// 2. CRTP 基类:模板参数是派生类自身
// -----------------------------
template<typename Child>
struct event_crtp : public event<state> {std::unique_ptr<state> dispatch(state& s) const override {// 第二次分发 -> 状态调用具体事件处理return s.on_event(*static_cast<const Child*>(this));}
};
// -----------------------------
// 3. 所有事件类型(通过 CRTP 实现 dispatch)
// -----------------------------
struct event_connect final : public event_crtp<event_connect> {explicit event_connect(std::string_view address) : address_(address) {}std::string_view address() const { return address_; }
private:std::string_view address_;
};
struct event_connected final : public event_crtp<event_connected> {};
struct event_disconnect final : public event_crtp<event_disconnect> {};
struct event_timeout final : public event_crtp<event_timeout> {};
// -----------------------------
// 4. 状态基类:定义所有 on_event 重载接口
// -----------------------------
class state : noncopyable {
public:virtual ~state() = default;virtual std::unique_ptr<state> on_event(const event_connect&) { return nullptr; }virtual std::unique_ptr<state> on_event(const event_connected&) { return nullptr; }virtual std::unique_ptr<state> on_event(const event_disconnect&) { return nullptr; }virtual std::unique_ptr<state> on_event(const event_timeout&) { return nullptr; }
};
// -----------------------------
// 5. 具体状态类定义和实现
// -----------------------------
// 空闲状态
class state_idle final : public state {
public:using state::on_event; // 保留默认处理std::unique_ptr<state> on_event(const event_connect& e) override {std::cout << "Idle -> Connecting to " << e.address() << "\n";return std::make_unique<class state_connecting>(std::string{e.address()});}
};
// 正在连接状态
class state_connecting final : public state {std::string address_;int n = 0;static constexpr int n_max = 3;
public:explicit state_connecting(std::string addr) : address_(std::move(addr)) {}std::unique_ptr<state> on_event(const event_connected&) override {std::cout << "Connecting -> Connected\n";return std::make_unique<class state_connected>();}std::unique_ptr<state> on_event(const event_timeout&) override {++n;if (n >= n_max) {std::cout << "Connecting -> Idle (timeout)\n";return std::make_unique<state_idle>();}std::cout << "Connecting: timeout #" << n << "\n";return nullptr;}
};
// 已连接状态
class state_connected final : public state {
public:std::unique_ptr<state> on_event(const event_disconnect&) override {std::cout << "Connected -> Idle\n";return std::make_unique<state_idle>();}
};
// -----------------------------
// 6. FSM 管理器模板
// -----------------------------
template<typename State, typename Event>
class fsm {std::unique_ptr<State> state_;
public:explicit fsm(std::unique_ptr<State> s) : state_(std::move(s)) {}void dispatch(const Event& e) {auto new_state = e.dispatch(*state_);if (new_state) {state_ = std::move(new_state);}}
};
// -----------------------------
// 7. 主程序:运行测试
// -----------------------------
int main() {fsm<state, event<state>> connection_fsm{std::make_unique<state_idle>()};connection_fsm.dispatch(event_connect{"example.com"});connection_fsm.dispatch(event_timeout{});connection_fsm.dispatch(event_timeout{});connection_fsm.dispatch(event_timeout{}); // 连接失败回到 idleconnection_fsm.dispatch(event_connect{"another.com"});connection_fsm.dispatch(event_connected{});connection_fsm.dispatch(event_disconnect{});
}
CRTP(Curiously Recurring Template Pattern)简析
原理
CRTP 是一种 模板与继承结合的技巧,派生类将自己作为模板参数传给基类:
template<typename Derived>
class Base {void foo() { static_cast<Derived*>(this)->bar(); }
};
class Derived : public Base<Derived> {void bar();
};
在这个 FSM 中的用法:
event_crtp<Child>
是模板基类,提供统一的dispatch()
实现。- 子类如
event_connect
继承event_crtp<event_connect>
,自动拥有 dispatch。 - 避免了每个事件类都手写
dispatch()
的重复代码。
双重分发完整链条:
fsm::dispatch()
调用事件的dispatch(state&)
→ 第一次分发(事件分发给状态);event_crtp::dispatch()
调用状态的on_event(*this)
→ 第二次分发(根据事件类型,进入状态类的特定重载);state_idle::on_event(const event_connect&)
等是根据事件类型决定的虚函数重载。
这就是双重分发(double dispatch)的完整实现。
总结:优点
特性 | 说明 |
---|---|
清晰的状态转换 | 每个状态类管理自己的转移逻辑 |
双重分发 | 利用虚函数支持“基于状态 + 事件”的派发 |
CRTP 简化事件定义 | 每个事件不再重复写 dispatch() |
面向对象扩展性好 | 增加新状态或事件都很简单 |
数据封装能力强 | event_connect 携带 address 数据 |
如果你想继续:
- 用
std::variant
实现替代这套双重分发; - 使用现代
std::visit
实现 FSM; - 用 Boost.SML 比较这套手写实现;
- 把状态保存成 JSON 等形式可序列化;
状态机实现的内容,具体涵盖了:
- 双重分发(Double Dispatch)
- Fold Expression 测试多个状态转换
- 对象模型的局限
- 事件为指针形式传递
- 对新操作和新事件的开放/封闭性分析
下面我逐点帮你解释、分析并提供对应的完整测试代码。
1. TESTING TRANSITIONS with fold expression
template<typename Fsm, typename... Events>
void dispatch(Fsm& fsm, const Events&... events)
{(fsm.dispatch(*events), ...); // C++17 fold expression
}
理解:
这段代码是用于测试状态机逻辑是否能按预期流转,通过传入多个事件(以 unique_ptr
形式),一次性全部派发。
2. 使用示例:
dispatch(fsm,std::make_unique<event_connect>("train-it.eu"),std::make_unique<event_timeout>(),std::make_unique<event_connected>(),std::make_unique<event_disconnect>());
理解:
- 每个
event_*
都是event<state>
的派生类,存储在unique_ptr
中。 dispatch
函数解包这些指针并调用fsm.dispatch(*e)
。- 通过这种方式可以方便模拟一连串状态转换行为。
3. 为什么需要 std::unique_ptr<event>
?
- 因为事件是通过虚函数进行分发的(需要多态);
- 所以必须以
event<state>
的指针(或引用)来传递; - 这里选择
std::unique_ptr<event<state>>
是为了资源管理更安全。
4. THE SLOW PART
这段指的是使用这种双重虚函数(dispatch 调用 -> on_event)实现方式:
运行时有两个虚函数跳转,这就是“慢”的部分。
fsm.dispatch(*event); // 1st virtual dispatch: event::dispatch()// 2nd virtual dispatch: state::on_event()
每次调用都涉及两个动态分发(vtable 调用):
- 事件 dispatch;
- 状态的 on_event 重载。
所以对于性能敏感场景,可以考虑替代机制,如:
std::variant
+std::visit
(编译期分发)Boost.SML
(静态状态机)
5. Double Dynamic Dispatch(双重分发)小结
优点:
特性 | 描述 |
---|---|
面向对象 | 易于扩展状态和事件 |
高解耦 | 状态与事件是解耦的类 |
多级继承支持 | 状态、事件可多层级继承 |
缺点:
问题 | 描述 |
---|---|
无法添加新操作 | 新的行为(如调试、日志)必须改基类 |
扩展受限 | 派生层次固定,不适合插件系统 |
性能偏低 | 双重虚函数跳转,不能内联优化 |
总结理解
双重分发的流程是:
fsm.dispatch(event_ptr)→ event_ptr->dispatch(state)→ state->on_event(event)
fold expression 调用流程是:
dispatch(fsm, event1, event2, event3);→ fsm.dispatch(*event1)→ fsm.dispatch(*event2)→ ...
最后:测试完整代码片段
template<typename Fsm, typename... Events>
void dispatch(Fsm& fsm, const Events&... events)
{(fsm.dispatch(*events), ...);
}
int main() {fsm<state, event<state>> fsm_machine{std::make_unique<state_idle>()};dispatch(fsm_machine,std::make_unique<event_connect>("train-it.eu"),std::make_unique<event_timeout>(),std::make_unique<event_connected>(),std::make_unique<event_disconnect>());
}