CppCon 2018 学习:STATE MACHINES BATTLEFIELD NAIVE VS STL VS BOOST
“STATE MACHINES BATTLEFIELD NAIVE VS STL VS BOOST”可以理解为一种对比分析,主要是比较用不同方式实现状态机(State Machine)的优缺点。通常这是软件设计中一个经典话题,尤其在 C++ 里,状态机实现可以有多种途径,下面是一个理解框架:
1. Naive 实现(手写状态机)
特点:
- 用
switch
语句或if-else
链手动管理状态转移。 - 简单直接,理解成本低。
- 代码通常比较冗长且难维护。
- 扩展和修改状态/行为时容易出错,增加复杂度。
举例:
enum class State { Idle, Running, Paused };
void handleEvent(State& state, Event e) {switch(state) {case State::Idle:if (e == Event::Start) state = State::Running;break;case State::Running:if (e == Event::Pause) state = State::Paused;else if (e == Event::Stop) state = State::Idle;break;// ...}
}
2. STL 实现(标准库辅助)
特点:
- 用 STL 容器、函数对象、
std::function
等来封装状态和事件处理。 - 更加模块化和抽象,代码更灵活。
- 仍然需要自己管理状态转移逻辑。
- 不需要外部依赖,轻量。
举例: - 用
std::map<State, std::function<void(Event)>>
来管理状态和事件映射。 - 用
std::variant
或std::optional
管理状态。
3. Boost.Statechart 或 Boost.SML 等 Boost 库实现
特点:
- Boost 提供的专门状态机库,功能强大且规范。
- 支持状态嵌套、并行状态、历史状态、条件转移等复杂功能。
- 写法规范,维护性和扩展性好。
- 但学习曲线较陡,代码依赖 Boost。
- 编译时间相对较长,代码更复杂。
举例:
用 Boost.Statechart 写一个状态机,状态和事件都是类,状态机框架自动帮你处理状态生命周期和转移。
总结对比
方面 | Naive | STL-based | Boost.Statechart |
---|---|---|---|
易用性 | 简单直观 | 中等,需要设计抽象 | 较复杂,需要学习 Boost 库 |
灵活性 | 低 | 高 | 很高 |
功能 | 基础状态转移 | 自定义功能 | 支持复杂状态、并行、嵌套等 |
维护成本 | 高 | 中等 | 低(依赖库完善,接口统一) |
依赖 | 无 | 无 | 依赖 Boost |
编译时间 | 快 | 快 | 较慢 |
这段内容看起来是BDD(Behavior-Driven Development,行为驱动开发)风格的测试用例描述,用来描述“连接”功能的行为场景。帮你理一下这段话的意思和结构:
BDD 样例说明 — “连接”功能
Feature: Connection
(功能:连接)
Scenario: Establish connection
(场景:建立连接)
- Given I don’t have a connection
假设当前没有连接 - When I receive a request to connect
当收到一个连接请求 - Then I should try to establish the connection
那么我应该尝试建立连接 - When I receive an established acknowledgement
当收到连接建立确认 - Then I should have been connected
那么此时连接已经建立成功
Scenario: Disconnect
(场景:断开连接)
…(未展开,但逻辑类似)
理解:
这段文字是在用BDD的Gherkin语法,描述“连接”的需求和预期行为。BDD通过“Given-When-Then”结构清晰地表达测试场景和步骤,方便开发和测试沟通需求,也利于自动化测试。
- Given:描述测试的初始状态。
- When:触发的事件或操作。
- Then:期望的结果或状态。
BDD(Behavior-Driven Development,行为驱动开发)是一种软件开发过程中的敏捷方法,核心思想是通过描述软件系统的行为来驱动开发和测试。它强调开发人员、测试人员和业务人员之间的沟通,确保大家对系统功能的理解一致。
BDD的核心概念:
- 行为驱动:关注软件应该做什么(行为),而不是怎么实现。
- 用例驱动:通过具体的“场景”描述需求,每个场景是一个真实业务流程的例子。
- 可读性强:用自然语言描述业务需求,非技术人员也能理解。
- 自动化测试:BDD的场景描述通常可以转化为自动化测试用例,实现开发和测试的自动化。
BDD常用的格式:Gherkin语法
用“Given-When-Then”结构来描述行为:
- Given(前置条件):系统当前的状态或背景。
- When(触发事件):用户或系统进行的操作。
- Then(期望结果):系统应表现出的行为或结果。
举个简单例子:
Feature: 用户登录Scenario: 输入正确用户名和密码成功登录Given 用户在登录页面When 用户输入正确的用户名和密码Then 用户应成功登录并看到欢迎页面
BDD的好处:
- 促进开发、测试和业务之间的沟通
- 明确需求,减少误解
- 自动化测试,确保功能符合预期
- 文档即代码,需求描述不会过时
提出设计“连接(Connection)”功能时的几个递进性考虑点,体现了在解决问题时常见的权衡目标:
理解这段话:
问题 / 动机(PROBLEM / MOTIVATION)
我们想要满足或实现“连接”这个需求,思考的关键是:
- 怎样满足/实现连接需求?
— 这是最基础的问题:功能怎么写才能达成目标。 - 怎样用最易读的方式实现连接?
— 不仅实现功能,还要代码易于理解,方便别人(甚至自己)读懂。 - 怎样用最易维护的方式实现连接?
— 易读是基础,维护性更进一步,代码写得清晰且结构合理,方便未来修改和扩展。 - 怎样用最高效的方式实现连接?
— 在保证易读易维护的同时,尽可能提高运行效率(性能、资源利用等)。
总结:
这段话表达了设计良好软件的一般目标:不仅要“能用”,还要“好用”(可读性、可维护性),“用得快”(效率)。在实际设计和实现中,这些目标往往需要权衡和兼顾。
内容主要是在讲 统一建模语言(UML)2.5 中的 状态机(State Machine),结合之前提到的“连接(Connection)”功能,做一个形式化的建模说明。
理解要点:
- UML状态机(State Machine)
UML状态机是一种用来描述系统状态和状态间转换的图形化模型。它帮助开发者以结构化方式描述对象的生命周期和行为。 - 版本 2.5
你引用的UML 2.5规范 是该语言较新的官方标准,详细定义了状态机的语法和语义。 - 状态机与Connection(连接)功能的结合
- 你提到的 “Feature: Connection” 表示这是状态机描述的功能模块。
- 里面有若干“Scenario”(场景),比如“Connect”、“Disconnect”等,这类似BDD里的场景,表示不同的状态变化路径。
- 场景对应状态机
- “Connect”场景对应状态机中从“无连接”状态到“已连接”状态的状态转换过程。
- “Disconnect”场景则对应断开连接的状态转换。
- 其他场景也可能对应其他状态和事件。
简单举例:
假设连接状态机:
- 状态:Disconnected → Connecting → Connected → Disconnecting → Disconnected
- 事件:请求连接、连接成功、断开请求、断开完成
- 转换:收到连接请求时,状态从 Disconnected 变成 Connecting,连接成功后变成 Connected,等等。
总结:
你在用UML 2.5状态机规范来形式化描述连接相关的各种行为(场景),这有助于系统设计、验证和沟通。
链接中有完整的标准文档,推荐详细阅读。
以下是基于您提供的状态机图生成的 Mermaid 状态机代码,用于表示连接状态的转换(Disconnected、Connecting、Connected)。Mermaid 的状态图使用 stateDiagram-v2
语法:
stateDiagram-v2[*] --> DisconnectedDisconnected --> Connecting : connect / establishConnected --> Connecting : timeoutConnecting --> Connected : establishedConnected --> Disconnected : disconnectConnected --> Connected : ping [is_valid] / reset_timeout
这部分内容讲的是使用 现代C++(例如 C++17/20)语法 来构建 有限状态机(State Machine) 的通用实现方式。具体来说:
内容概览
你正在看的内容属于一个状态机的实现部分,包括四个核心组成:
1. Events(事件)
这些是状态机响应的输入:
struct connect {};
struct established {};
struct ping {};
struct disconnect {};
struct timeout {};
这些是“标签类型”(tag types),用作事件标识。例如你可以用 connect{}
来触发连接过程。
2. Guards(守卫条件)
这是判断一个事件是否允许发生的逻辑,返回布尔值:
constexpr auto is_valid = [](auto const& event) { return true; };
目前它对所有事件都返回 true
,你可以根据需要扩展,比如:
constexpr auto is_valid = [](auto const& event) {return !std::is_same_v<decltype(event), timeout>;
};
表示 timeout
不被认为是合法事件。
3. Actions(动作)
这些是在状态转换时要执行的副作用(side-effect),比如日志、网络调用、状态更新等:
constexpr auto establish = [] { std::puts("establish"); };
constexpr auto close = [] { std::puts("close"); };
constexpr auto reset_timeout = [] { std::puts("reset_timeout"); };
这些是 lambda 函数,你可以在状态机配置中作为动作绑定到特定事件。
4. 用途
这些事件、守卫和动作是用来驱动状态转换的典型元素,通常配合如下状态机框架使用:
- Boost::SML
- 或者手写简易 FSM 模板
例如在 Boost.SML 中,你会这样写:
using namespace boost::sml;
struct ConnectionStateMachine {auto operator()() const {return make_transition_table(*state<Disconnected> + event<connect> [is_valid] / establish = state<Connecting>,state<Connecting> + event<established> / reset_timeout = state<Connected>,state<Connected> + event<timeout> / close = state<Disconnected>);}
};
总结
这段代码是 构建一个现代 C++ 状态机系统 的基础部分。你现在拥有:
类型 | 说明 |
---|---|
struct connect {} 等 | 表示事件 |
is_valid | 守卫函数 |
establish , close | 状态转换时的动作 |
这部分介绍了使用 朴素(naive)方式 实现状态机的做法,采用的是 C++98 特性 —— 也就是 没有现代 C++ 的状态系统/库,只能靠布尔值、if/else
手动管理状态。
核心结构:Naive 状态机实现
class Connection {// 用多个布尔值表示状态bool disconnected = true, connected = false, connecting = false;// 事件处理函数:connectconstexpr void process_event(connect const&) {if (disconnected) {establish(); // 执行动作connected = disconnected = false; // 重置所有状态connecting = true; // 设置新状态}}// 事件处理函数:pingconstexpr void process_event(ping const& event) {if (connected && is_valid(event)) {reset_timeout(); // 执行动作// 状态保持 connected}}
};
分析与理解
1. 使用布尔值模拟状态
bool disconnected = true, connected = false, connecting = false;
用 3 个布尔值来记录状态。这是低级但常见的做法,但容易出错(例如:多个状态同时为 true
)。
优点:
- 简单、容易实现。
缺点: - 状态之间互斥性需要手动管理,容易出现多个状态同时为
true
。 - 状态流转逻辑杂糅在代码中,不易维护。
2. 每个事件写一个 process_event(...)
函数
constexpr void process_event(connect const&) {if (disconnected) {establish();...}
}
这是最原始的方式 —— 用 if
来判断当前状态是否匹配,再执行对应操作,更新状态。
3. 示例中的逻辑
connect
事件触发:- 如果当前是
disconnected
,就执行establish()
,然后切换为connecting
。
- 如果当前是
ping
事件触发:- 如果当前是
connected
且事件合法,就调用reset_timeout()
,不切换状态。
- 如果当前是
潜在问题
问题 | 描述 |
---|---|
状态交叉 | 同时 connected == true && connecting == true 是非法但未被防止。 |
可维护性差 | 随着事件种类增多,if/else 结构会变复杂 |
无状态图支持 | 不支持从配置或可视化工具中自动生成 |
更现代的替代方案
- 使用
enum class State {Disconnected, Connecting, Connected}
替代布尔值。 - 使用 状态转移表(如 Boost.SML)。
- 使用 UML 生成工具 或 DSL(领域特定语言)来自动生成代码。
总结
你现在看到的:
是一个非常基础、朴素(naive)的状态机实现方式,适用于入门和理解状态转换逻辑,但不适合大型、复杂系统。
#include <cstdio>
// ---------- 事件定义 ----------
struct connect {}; // 发起连接请求
struct established {}; // 连接建立完成
struct ping {}; // 保活请求(连接存在期间)
struct disconnect {}; // 主动断开连接
struct timeout {}; // 超时(被动断开)
// ---------- 动作(行为) ----------
constexpr auto establish = [] { std::puts("Action: establish"); }; // 模拟连接建立动作
constexpr auto close = [] { std::puts("Action: close"); }; // 模拟断开连接动作
constexpr auto reset_timeout = [] { std::puts("Action: reset_timeout"); }; // 模拟超时重置行为
// ---------- 条件判断(Guard 条件) ----------
constexpr auto is_valid = [](auto const&) { return true; }; // 简化:总是合法
// ---------- 连接状态机 ----------
class Connection {// 状态通过布尔变量显式管理bool disconnected = true; // 初始状态为断开bool connecting = false;bool connected = false;
public:// 处理 connect 事件:从断开 → 连接中void process_event(const connect&) {if (disconnected) {establish(); // 执行动作disconnected = false; // 清除旧状态connected = false;connecting = true; // 设置新状态std::puts("State: Connecting");}}// 处理 established 事件:从连接中 → 已连接void process_event(const established&) {if (connecting) {connecting = false;connected = true;std::puts("State: Connected");}}// 处理 ping 事件:仅当处于连接状态时响应void process_event(const ping& event) {if (connected && is_valid(event)) {reset_timeout(); // 重置超时计时器}}// 处理 disconnect 事件:从连接中/已连接 → 断开void process_event(const disconnect&) {if (connected || connecting) {close(); // 执行断开动作disconnected = true; // 设置状态connected = false;connecting = false;std::puts("State: Disconnected");}}// 处理 timeout 事件:仅当已连接时响应,转为断开void process_event(const timeout&) {if (connected) {close(); // 被动断开connected = false;disconnected = true;std::puts("State: Disconnected (timeout)");}}
};
int main() {Connection conn;std::puts("== Begin connection process ==");conn.process_event(connect{}); // 发起连接conn.process_event(established{}); // 模拟连接完成conn.process_event(ping{}); // 保活(有效)conn.process_event(disconnect{}); // 主动断开std::puts("== Simulate timeout after reconnect ==");conn.process_event(connect{}); // 再次连接conn.process_event(established{}); // 再次连接完成conn.process_event(timeout{}); // 超时触发断开return 0;
}
对 Naive 状态机实现 两种方式的总结:一种是使用 if/else + bool
标志位,另一种是使用 switch + enum
。以下是对这两种实现的总结、优缺点对比,以及当前 switch/enum
实现的详细解读。
NAIVE - IF/ELSE - SUMMARY (布尔值实现状态)
优点 (+):✓ 可内联编译(inlined),GCC/Clang 优化好✓ 无堆内存使用(No heap usage)✓ 内存占用小(3 bytes for 3 bools)
中性 (~):• 占用少量内存(Small-ish memory footprint)
缺点 (-):✗ 难以复用、扩展性差✗ 状态之间的排他性难以保证(多个 bool 同时为 true)✗ 添加新状态易出错(不易维护)
==> 适合非常简单的状态管理,但不推荐用于复杂系统
NAIVE - SWITCH/ENUM - IMPLEMENTATION (使用 enum class + switch
)
核心思想:
- 使用
enum class State
明确只可能有一个状态。 - 使用
switch(state)
控制状态转移逻辑。 - 更清晰、逻辑上更严谨,避免多状态“同时为真”的问题。
示例代码分析:
class Connection {// 明确的状态表示:仅一个状态有效enum class State : char {DISCONNECTED,CONNECTING,CONNECTED} state = State::DISCONNECTED;
public:constexpr void process_event(connect const&) {switch (state) {default: break;case State::DISCONNECTED:establish();state = State::CONNECTING;break;}}constexpr void process_event(ping const& event) {switch (state) {default: break;case State::CONNECTED:if (is_valid(event)) {reset_timeout();}break;}}// 你可以继续添加其他事件,比如 established、timeout、disconnect 等
};
总结:switch/enum
vs bool
特性 | if/else + bool | switch + enum |
---|---|---|
状态排他性 | 不强制 | 强制(只有一个 state 有效) |
可读性 | 中等 | 更清晰 |
扩展性 | 差,新增状态容易出错 | 容易维护 |
内存占用 | 通常为 3 字节(3 个 bool) | 更少(1 字节 enum class) |
复用性 | 难复用 | 容易封装成通用状态机逻辑 |
适合复杂状态逻辑? | 否 | 是(结构清晰) |
结论
如果你要实现一个结构更清晰、状态一致性更强、维护更简单的状态机:
推荐用 enum class + switch
的方式。
下面是一个使用 enum class + switch
方式实现的 完整的状态机示例代码,模拟连接状态的转移过程,包括连接、确认、ping、断开和超时处理。
示例:状态机 - Connection
类(使用 enum class
+ switch
)
#include <cstdio>
// ===========================
// 定义事件类型(状态机接受的输入)
// ===========================
struct connect {}; // 连接请求事件
struct established {}; // 连接建立完成事件
struct ping {}; // 心跳或 ping 事件
struct disconnect {}; // 主动断开连接事件
struct timeout {}; // 超时断开事件
// ===========================
// 定义动作(副作用)
// ===========================
constexpr auto establish = [] { std::puts("Action: establish"); }; // 执行建立连接
constexpr auto close = [] { std::puts("Action: close"); }; // 执行关闭连接
constexpr auto reset_timeout = [] { std::puts("Action: reset_timeout"); }; // 重置超时计时器
// ===========================
// 定义事件的有效性检查(Guard)
// ===========================
constexpr auto is_valid = [](auto const&) { return true; }; // 默认所有事件都有效
// ===========================
// 连接状态机类
// ===========================
class Connection {
public:// 所有可能的连接状态(枚举类型)enum class State : char {DISCONNECTED, // 断开状态CONNECTING, // 正在连接中CONNECTED // 已连接状态};// 当前状态初始化为 DISCONNECTEDState state = State::DISCONNECTED;// 处理 connect 事件(只有在断开状态才有效)void process_event(const connect&) {switch (state) {case State::DISCONNECTED:establish(); // 执行连接建立逻辑state = State::CONNECTING; // 状态变更为连接中std::puts("State: CONNECTING");break;default:std::puts("Ignored: connect in current state"); // 非断开状态下忽略break;}}// 处理 established 事件(只有在连接中状态才有效)void process_event(const established&) {switch (state) {case State::CONNECTING:state = State::CONNECTED; // 状态变更为已连接std::puts("State: CONNECTED");break;default:std::puts("Ignored: established in current state"); // 其他状态下忽略break;}}// 处理 ping 事件(只有在已连接状态才处理)void process_event(const ping& event) {switch (state) {case State::CONNECTED:if (is_valid(event)) {reset_timeout(); // 如果有效,重置超时}break;default:std::puts("Ignored: ping in current state"); // 其他状态下忽略break;}}// 处理 disconnect 事件(连接中或已连接状态下可断开)void process_event(const disconnect&) {switch (state) {case State::CONNECTED:case State::CONNECTING:close(); // 执行关闭逻辑state = State::DISCONNECTED; // 状态变更为断开std::puts("State: DISCONNECTED");break;default:std::puts("Ignored: disconnect in current state");break;}}// 处理 timeout 事件(只有已连接状态可触发超时断开)void process_event(const timeout&) {switch (state) {case State::CONNECTED:close(); // 超时断开连接state = State::DISCONNECTED;std::puts("State: DISCONNECTED (timeout)");break;default:std::puts("Ignored: timeout in current state");break;}}
};
// ===========================
// 主函数 - 模拟状态机运行流程
// ===========================
int main() {Connection conn;std::puts("== Start ==");conn.process_event(connect{}); // 发起连接conn.process_event(established{}); // 连接建立conn.process_event(ping{}); // 收到 ping 心跳conn.process_event(disconnect{}); // 主动断开连接std::puts("== Reconnect & timeout ==");conn.process_event(connect{}); // 再次连接conn.process_event(established{}); // 再次建立连接conn.process_event(timeout{}); // 模拟超时断开return 0;
}
输出示例:
== Start ==
Action: establish
State: CONNECTING
State: CONNECTED
Action: reset_timeout
Action: close
State: DISCONNECTED
== Reconnect & timeout ==
Action: establish
State: CONNECTING
State: CONNECTED
Action: close
State: DISCONNECTED (timeout)
特点总结:
- 明确每个状态的合法事件。
- 状态切换统一在
switch
中管理,逻辑清晰。 - 易于扩展和维护。
- 没有动态内存分配,性能稳定。
- 更接近正式状态机的组织结构。
使用 NAIVE - SWITCH/ENUM 方法实现状态机的优缺点。我们逐条解释它的含义和背后的动机,帮助你真正理解它在实际开发中的利与弊:
NAIVE - SWITCH/ENUM - SUMMARY 解读
优点:
(+) Inlined (gcc/clang)
- 使用
constexpr
和switch
+enum class
,编译器能够完全内联(inline)处理逻辑。 - 意味着在没有虚函数或动态分发的情况下,函数调用几乎没有开销。
- 编译器可以优化整个状态转移过程为纯指令流,非常高效。
(+) Small memory footprint
sizeof(Connection) == 1b
:- 状态只由一个
enum class State : char
表示,占用 1 字节。 - 没有其他成员变量(不像布尔变量组合的方式)。
- 非常适合嵌入式开发或对内存敏感的场景。
- 状态只由一个
(+) No heap usage
- 不使用
new
/delete
,所有对象都在栈上分配。 - 没有指针操作,无需担心内存泄漏、生命周期管理、智能指针等问题。
- 简单直接,符合现代 C++ 的 RAII 理念。
缺点:
(-) Hard to reuse
- 状态机逻辑硬编码在
Connection
类中,事件和状态处理逻辑紧耦合。 - 想要扩展状态机或抽象出通用行为非常困难。
- 不易复用到其他场景(例如另一个类似的状态机)。
- 很难进行单元测试、Mock 或替换内部逻辑。
总结一句话:
这种方法适合逻辑简单、性能敏感、不需要高扩展性的状态机实现。
建议什么时候使用:
- 小型项目
- 嵌入式系统
- 状态逻辑固定、变化少
- 对性能要求极高、不能用虚函数或模板元编程的环境
不推荐用于:
- 状态过多、复杂度高
- 逻辑要频繁变更或可配置
- 多个状态机共享通用行为
- 希望以更现代/面向对象/组合方式组织代码(比如 Boost.SML、SCXML、State Design Pattern)
内容是在讲用 继承 / 状态模式(State Pattern) 实现状态机的方式。我们来逐步分析和解释这段实现方式的核心思想和结构。
核心理念:状态模式(State Pattern)
状态模式是面向对象设计中的一种经典模式,用于:
- 将不同状态下的行为封装成类。
- 每个状态都有自己的类处理事件。
- Context(如 Connection)委托当前状态对象处理行为,并根据需要切换状态。
基础接口:State
struct State {virtual ~State() noexcept = default;virtual void process_event(connect const&) = 0;virtual void process_event(ping const&) = 0;virtual void process_event(established const&) = 0;virtual void process_event(timeout const&) = 0;virtual void process_event(disconnect const&) = 0;
};
解释:
- 抽象基类
State
声明了所有事件的处理接口。 - 每种状态(如
Disconnected
,Connected
,Connecting
)会继承并实现这些方法。 - 这是 多态派发(virtual dispatch) 的基础。
示例状态类:Disconnected
struct Disconnected : State {Connection& connection;void process_event(connect const&) override final {establish();connection.change_state<Connecting>();}
};
解释:
- 表示“断开连接”状态。
- 当收到
connect
事件:- 执行动作
establish()
- 然后通过
connection.change_state<Connecting>()
进入下一状态。
- 执行动作
每个状态类负责自己的逻辑,避免了大量
if
/switch
。
另一个状态类:Connected
struct Connected : State {Connection& connection;void process_event(ping const& event) override final {if (is_valid(event)) {reset_timeout();}}// 可继续定义其他事件处理函数…
};
Connection 类(上下文)会做什么?
虽然未完整展示,但从语境推断:
class Connection {std::unique_ptr<State> state;
public:template<typename StateType>void change_state() {state = std::make_unique<StateType>(*this);}template<typename Event>void process_event(Event const& e) {state->process_event(e);}
};
解释:
Connection
拥有当前状态的指针(多态指针std::unique_ptr<State>
)- 所有事件都委托给当前
state
处理 change_state<>()
用于切换状态,生成对应状态类实例
优点 VS 缺点:
优点 | 缺点 |
---|---|
易于扩展、每种状态独立 | 使用虚函数,有一点运行时开销 |
逻辑清晰,符合 OOP 原则 | 每个状态类单独文件可能过多 |
没有 switch /if 混乱逻辑 | 对初学者理解有一定门槛 |
更容易进行单元测试和维护 | 每次状态切换会动态分配内存(可优化) |
适用场景:
- 状态众多、状态逻辑复杂的系统
- 希望代码高度解耦、可维护、可测试
- 项目采用面向对象设计风格(如 Qt)
#include <memory>
#include <type_traits>
#include <cstdio>
#include <string>
// -------------------- 事件定义 --------------------
// 不同事件结构体,用来区分状态机的输入事件
struct connect {}; // 连接请求事件
struct established {}; // 连接已建立事件
struct ping {}; // 保活包事件
struct disconnect {}; // 断开连接事件
struct timeout {}; // 超时事件
// -------------------- 动作定义 --------------------
// 对应状态机中的副作用动作,比如打印动作、重置超时等
constexpr auto establish = [] { std::puts("Action: establish"); };
constexpr auto close_ = [] { std::puts("Action: close"); };
constexpr auto reset_timeout = [] { std::puts("Action: reset_timeout"); };
// 事件有效性检查,这里简单返回 true,表示所有事件都有效
constexpr auto is_valid = [](auto const&) { return true; };
// -------------------- 前向声明 --------------------
// 连接上下文类,需要提前声明,供状态类调用
class Connection;
// -------------------- 状态基类 --------------------
// 状态基类定义所有事件的虚接口,默认事件处理是忽略(打印提示)
struct State {virtual ~State() noexcept = default;// 各种事件的处理接口,默认调用 ignore 打印忽略信息virtual void process_event(Connection&, connect const&) { ignore("connect"); }virtual void process_event(Connection&, established const&) { ignore("established"); }virtual void process_event(Connection&, ping const&) { ignore("ping"); }virtual void process_event(Connection&, disconnect const&) { ignore("disconnect"); }virtual void process_event(Connection&, timeout const&) { ignore("timeout"); }
protected:// 默认忽略事件的打印函数void ignore(const char* evt) const { std::printf("Ignored: %s in current state\n", evt); }
};
// -------------------- 状态类:Disconnected --------------------
// 断开连接状态,实现 connect 事件处理
struct Disconnected : State {void process_event(Connection& conn, connect const&) override;
};
// -------------------- 状态类:Connecting --------------------
// 连接中状态,实现 established 和 disconnect 事件处理
struct Connecting : State {void process_event(Connection& conn, established const&) override;void process_event(Connection& conn, disconnect const&) override;
};
// -------------------- 状态类:Connected --------------------
// 已连接状态,实现 ping、disconnect、timeout 事件处理
struct Connected : State {void process_event(Connection& conn, ping const& event) override;void process_event(Connection& conn, disconnect const&) override;void process_event(Connection& conn, timeout const&) override;
};
// -------------------- 状态机上下文类 --------------------
// 状态机类,持有当前状态对象,委托事件处理给状态类
class Connection {// 用智能指针管理当前状态对象std::unique_ptr<State> state;
public:// 构造时初始状态设为 DisconnectedConnection() { change_state<Disconnected>(); }// 状态切换模板方法,创建新状态对象替换旧的template <typename StateType>void change_state() {// 确保传入的状态类型继承自 Statestatic_assert(std::is_base_of<State, StateType>::value, "Must inherit from State");state = std::make_unique<StateType>();// 打印当前状态类型名(typeid.name 会比较难读)std::puts(("State: " + std::string(typeid(StateType).name())).c_str());}// 事件处理模板方法,调用当前状态对应事件处理template <typename Event>void process_event(const Event& evt) {state->process_event(*this, evt);}
};
// -------------------- 状态行为实现 --------------------
// Disconnected 状态处理 connect 事件,发起建立连接动作并切换到 Connecting 状态
void Disconnected::process_event(Connection& conn, connect const&) {establish();conn.change_state<Connecting>();
}
// Connecting 状态处理 established 事件,切换到 Connected 状态
void Connecting::process_event(Connection& conn, established const&) {conn.change_state<Connected>();
}
// Connecting 状态处理 disconnect 事件,发起关闭动作并回到 Disconnected 状态
void Connecting::process_event(Connection& conn, disconnect const&) {close_();conn.change_state<Disconnected>();
}
// Connected 状态处理 ping 事件,验证有效则重置超时
void Connected::process_event(Connection& conn, ping const& event) {if (is_valid(event)) {reset_timeout();}
}
// Connected 状态处理 disconnect 事件,关闭连接并切换到 Disconnected
void Connected::process_event(Connection& conn, disconnect const&) {close_();conn.change_state<Disconnected>();
}
// Connected 状态处理 timeout 事件,关闭连接,打印提示并切换回 Disconnected
void Connected::process_event(Connection& conn, timeout const&) {close_();std::puts("State: Disconnected (timeout)");conn.change_state<Disconnected>();
}
// -------------------- 测试主函数 --------------------
int main() {Connection conn;std::puts("== Start ==");conn.process_event(connect{}); // 触发连接请求,切换到 Connectingconn.process_event(established{}); // 连接成功,切换到 Connectedconn.process_event(ping{}); // 发送保活,重置超时conn.process_event(disconnect{}); // 断开连接,回到 Disconnectedstd::puts("\n== Reconnect & timeout ==");conn.process_event(connect{}); // 重新连接conn.process_event(established{}); // 连接成功conn.process_event(timeout{}); // 触发超时断开return 0;
}
这段代码实现了一个基于**继承和状态模式(State Pattern)**的连接状态机,核心思想和工作流程可以总结为:
核心结构
- 事件(Event):
connect
,established
,ping
,disconnect
,timeout
— 不同事件触发状态机行为。 - 动作(Action):
establish()
,close_()
,reset_timeout()
— 状态转换时执行的副作用(比如打印信息、重置超时计时器等)。 - 状态基类
State
:- 定义虚接口处理所有事件。
- 默认实现是“忽略”事件并打印提示。
- 具体状态类
Disconnected
,Connecting
,Connected
:- 重写对应事件的处理函数。
- 通过调用上下文
Connection
的change_state
方法完成状态转换。
- 上下文类
Connection
:- 持有当前状态(智能指针指向
State
基类派生对象)。 - 所有事件传给当前状态处理。
- 负责状态切换。
- 持有当前状态(智能指针指向
工作流程示例
- 开始是
Disconnected
状态。 - 收到
connect
事件,调用Disconnected::process_event
:- 执行动作
establish()
(打印“Action: establish”) - 状态切换到
Connecting
。
- 执行动作
- 收到
established
事件,调用Connecting::process_event
:- 状态切换到
Connected
。
- 状态切换到
- 收到
ping
事件,调用Connected::process_event
:- 调用
reset_timeout()
(打印“Action: reset_timeout”)。
- 调用
- 收到
disconnect
事件,调用Connected::process_event
:- 调用
close_()
(打印“Action: close”) - 状态切换回
Disconnected
。
- 调用
设计优点
- 清晰的状态划分:每个状态独立实现其事件处理,符合单一职责原则。
- 易于扩展和维护:新增状态只需继承
State
并实现对应事件。 - 状态切换灵活:上下文只负责管理状态指针和切换,具体行为由状态对象负责。
设计缺点
- 性能开销:每次状态切换都会分配一个新的状态对象,使用了动态内存(智能指针管理),且事件调用是虚函数调用。
- 复杂性较高:代码结构复杂,尤其是对初学者而言理解继承和虚函数调用链可能比较难。
- 状态数据共享受限:如果状态间需要共享数据,需要在
Connection
中添加成员变量,状态类通过引用访问。
总结
该代码是经典状态模式的实现范例,适合状态复杂且行为多样的状态机设计。它将状态的行为封装在不同类中,通过继承和多态实现动态状态切换,代码扩展性和可维护性较好,适合中大型项目的状态管理需求。
总结一下这段 INHERITANCE / STATE PATTERN 实现的优缺点:
INHERITANCE / STATE PATTERN - 总结
优点 (+):
- 易于扩展和重用
利用面向对象特性,通过继承和多态组织代码,新增状态只需新增类,代码结构清晰,符合单一职责和开闭原则。
中性 (~): - 较高的内存占用
每个状态是一个独立对象,且状态切换会动态分配新的对象,导致比简单枚举方案更高的内存开销。
缺点 (-): - 堆内存使用/动态分配
状态机通过std::unique_ptr
动态分配和释放状态对象,增加了运行时开销和碎片风险。 - 函数调用无法内联/虚函数开销
事件处理通过虚函数调用,编译器一般难以内联和去虚函数调用(devirtualize),导致性能不如直接写switch-case
。
适用场景
- 当状态行为复杂、状态数量多且状态间行为高度异同时,这种方式能帮助清晰地组织代码和职责。
- 不适合对性能和内存极端敏感的场合,尤其是嵌入式或实时系统。
这段用 std::variant
实现状态机的代码示例,核心思路如下:
class Connection {// 状态结构体定义,每个状态可以扩展保存更多数据struct Disconnected { }; // 断开状态struct Connecting { }; // 连接中状态struct Connected { }; // 已连接状态// 使用 std::variant 表示当前活动状态,保证只有一个状态生效std::variant<Disconnected, Connecting, Connected> state = Disconnected{};// 初始状态为 Disconnected
public:// 处理 connect 事件的函数constexpr void process_event(connect const&) {// 使用 std::visit 根据当前状态执行不同的 lambdastd::visit(overload{ // overload 是用于多lambda重载的辅助结构(需要定义)// 如果当前状态是 Disconnected,则执行以下动作:[&](Disconnected) {establish(); // 触发建立连接动作state = Connecting{}; // 切换状态到 Connecting},// 其他状态(Connecting、Connected)都不做任何操作[](auto) { /* no changes... */ }}, state);}// 处理 ping 事件的函数void process_event(ping const& event) {// 使用 std::get_if 判断当前是否是 Connected 状态指针if (std::get_if<Connected>(&state) && is_valid(event)) {reset_timeout(); // 如果事件有效,重置超时计时器}// 其余状态下 ping 事件不影响状态机,保持当前状态}
};
逐行注释解释
struct Disconnected { };
状态结构体,没有数据,仅标识状态类型。std::variant<Disconnected, Connecting, Connected> state = Disconnected{};
用std::variant
来存储当前状态实例,保证在某一时刻只处于一种状态。constexpr void process_event(connect const&)
处理connect
事件的函数。std::visit(overload{ ... }, state);
std::visit
根据state
当前持有的状态调用对应的 lambda 处理事件。& (Disconnected) { establish(); state = Connecting{}; }
如果当前是Disconnected
,触发establish
动作并切换到Connecting
。[](auto) {}
其他状态(Connecting
,Connected
)不做任何动作。void process_event(ping const& event)
处理ping
事件。if (std::get_if<Connected>(&state) && is_valid(event))
仅当当前状态是Connected
并且事件有效时,执行动作。reset_timeout();
重置连接超时。
额外说明:
overload
辅助结构:
overload
是一个小技巧,可以将多个lambda合并成一个可调用对象,用于std::visit
,比如:template<class... Ts> struct overload : Ts... { using Ts::operator()...; }; template<class... Ts> overload(Ts...) -> overload<Ts...>;
- 优点:
- 无需动态内存分配
- 类型安全,状态切换简单明了
- 易于维护和扩展
- 缺点:
- 每次事件处理都调用
std::visit
有一定开销 - 状态处理逻辑分散在多个lambda中,复杂逻辑下可读性稍差
- 每次事件处理都调用
#include <iostream>
#include <variant>
#include <string>
#include <type_traits>
// -------------------- 事件定义 --------------------
// 事件类型,表示不同的输入事件
struct connect {};
struct established {};
struct ping {};
struct disconnect {};
struct timeout {};
// -------------------- 动作定义 --------------------
// 状态机在处理事件时执行的动作,通常是副作用(打印、重置等)
constexpr auto establish = [] { std::puts("Action: establish"); };
constexpr auto close_ = [] { std::puts("Action: close"); };
constexpr auto reset_timeout = [] { std::puts("Action: reset_timeout"); };
// 事件有效性检测,这里简单返回 true
constexpr auto is_valid = [](auto const&) { return true; };
// -------------------- overload 辅助结构 --------------------
// 用于 std::visit 调用时合并多个 lambda,方便写多态访问
template <class... Ts>
struct overload : Ts... {using Ts::operator()...;
};
template <class... Ts>
overload(Ts...) -> overload<Ts...>;
// -------------------- 状态机类 --------------------
class Connection {
public:// 三个状态结构体,后续可扩展携带数据struct Disconnected {};struct Connecting {};struct Connected {};// 当前状态,存储在 std::variant 中,只能处于一个状态std::variant<Disconnected, Connecting, Connected> state = Disconnected{};// 处理 connect 事件void process_event(connect const&) {std::visit(overload{[&](Disconnected) {establish(); // 发起建立连接动作state = Connecting{}; // 切换到 Connecting 状态std::puts("State: Connecting");},[](auto) {// 其他状态忽略 connect 事件std::puts("Ignored: connect in current state");}},state);}// 处理 established 事件void process_event(established const&) {std::visit(overload{[&](Connecting) {state = Connected{}; // 切换到 Connected 状态std::puts("State: Connected");},[](auto) {// 其他状态忽略 established 事件std::puts("Ignored: established in current state");}},state);}// 处理 ping 事件void process_event(ping const& event) {// 仅 Connected 状态且事件有效时处理if (std::get_if<Connected>(&state) && is_valid(event)) {reset_timeout(); // 重置超时计时} else {std::puts("Ignored: ping in current state");}}// 处理 disconnect 事件void process_event(disconnect const&) {std::visit(overload{[&](Connecting) {close_(); // 关闭连接动作state = Disconnected{}; // 切换到 Disconnectedstd::puts("State: Disconnected");},[&](Connected) {close_();state = Disconnected{};std::puts("State: Disconnected");},[](auto) { std::puts("Ignored: disconnect in current state"); }},state);}// 处理 timeout 事件void process_event(timeout const&) {std::visit(overload{[&](Connected) {close_(); // 超时关闭连接std::puts("State: Disconnected (timeout)");state = Disconnected{}; // 切换到 Disconnected},[](auto) { std::puts("Ignored: timeout in current state"); }},state);}
};
// -------------------- 测试 --------------------
int main() {Connection conn;std::puts("== Start ==");conn.process_event(connect{}); // 从 Disconnected 发起连接,进入 Connectingconn.process_event(established{}); // 连接建立,进入 Connectedconn.process_event(ping{}); // 发送保活包,重置超时conn.process_event(disconnect{}); // 断开连接,返回 Disconnectedstd::puts("\n== Reconnect & timeout ==");conn.process_event(connect{}); // 重新连接,进入 Connectingconn.process_event(established{}); // 连接成功,进入 Connectedconn.process_event(timeout{}); // 超时断开连接,返回 Disconnectedreturn 0;
}
这段代码实现了一个使用 C++17 std::variant
来管理状态的 状态机,该状态机模拟了一个简单的连接过程,涉及三个状态和五种事件:connect
, established
, ping
, disconnect
, 和 timeout
。
下面是对代码的逐步解释:
事件定义
struct connect {};
struct established {};
struct ping {};
struct disconnect {};
struct timeout {};
这些 struct
类型表示不同的事件,用来触发状态机状态的转换。
动作定义
constexpr auto establish = [] { std::puts("Action: establish"); };
constexpr auto close_ = [] { std::puts("Action: close"); };
constexpr auto reset_timeout = [] { std::puts("Action: reset_timeout"); };
constexpr auto is_valid = [](auto const&) { return true; };
这些 constexpr
动作(通过 lambda 表达式实现)模拟了不同的操作,如建立连接、关闭连接和重置超时等。is_valid
只是一个简单的验证函数,总是返回 true
。
overload 辅助结构
template <class... Ts>
struct overload : Ts... {using Ts::operator()...;
};
template <class... Ts>
overload(Ts...) -> overload<Ts...>;
这段代码定义了一个帮助结构体 overload
,它允许多个 lambda 函数结合在一起,使得 std::visit
能够在不同状态下调用不同的行为。std::visit
会将 std::variant
中的状态类型与各个 lambda 进行匹配。
状态机类
class Connection {
public:struct Disconnected {};struct Connecting {};struct Connected {};std::variant<Disconnected, Connecting, Connected> state = Disconnected{};
Connection
类模拟一个连接的状态机。Disconnected
,Connecting
, 和Connected
分别表示连接的三个状态。std::variant
被用来表示当前的状态,初始化为Disconnected
。
事件处理
connect 事件
void process_event(connect const&) {std::visit(overload{[&](Disconnected) {establish(); // 发起建立连接动作state = Connecting{}; // 切换到 Connecting 状态std::puts("State: Connecting");},[](auto) {// 其他状态忽略 connect 事件std::puts("Ignored: connect in current state");}}, state);
}
- 当
connect
事件发生时,如果当前状态是Disconnected
,则触发establish()
动作,表示建立连接,并将状态切换到Connecting
。 - 对于其他状态(
Connecting
或Connected
),connect
事件会被忽略。
established 事件
void process_event(established const&) {std::visit(overload{[&](Connecting) {state = Connected{}; // 切换到 Connected 状态std::puts("State: Connected");},[](auto) {// 其他状态忽略 established 事件std::puts("Ignored: established in current state");}}, state);
}
established
事件表示连接成功。在Connecting
状态时,状态机将切换到Connected
状态。- 对于其他状态(如
Disconnected
或Connected
),该事件会被忽略。
ping 事件
void process_event(ping const& event) {if (std::get_if<Connected>(&state) && is_valid(event)) {reset_timeout(); // 重置超时计时} else {std::puts("Ignored: ping in current state");}
}
ping
事件仅在Connected
状态下有效。只有在Connected
状态时,才会调用reset_timeout()
来重置超时计时。- 对于其他状态,
ping
事件会被忽略。
disconnect 事件
void process_event(disconnect const&) {std::visit(overload{[&](Connecting) {close_(); // 关闭连接动作state = Disconnected{}; // 切换到 Disconnectedstd::puts("State: Disconnected");},[&](Connected) {close_();state = Disconnected{};std::puts("State: Disconnected");},[](auto) { std::puts("Ignored: disconnect in current state"); }}, state);
}
disconnect
事件用于断开连接。如果当前状态是Connecting
或Connected
,都会执行close_()
操作并切换回Disconnected
状态。- 对于其他状态,
disconnect
事件会被忽略。
timeout 事件
void process_event(timeout const&) {std::visit(overload{[&](Connected) {close_(); // 超时关闭连接std::puts("State: Disconnected (timeout)");state = Disconnected{}; // 切换到 Disconnected},[](auto) { std::puts("Ignored: timeout in current state"); }}, state);
}
timeout
事件只在Connected
状态下有效,表示连接超时。此时会执行close_()
操作并切换到Disconnected
状态。- 对于其他状态(如
Disconnected
或Connecting
),该事件会被忽略。
测试主函数
int main() {Connection conn;std::puts("== Start ==");conn.process_event(connect{}); // 从 Disconnected 发起连接,进入 Connectingconn.process_event(established{}); // 连接建立,进入 Connectedconn.process_event(ping{}); // 发送保活包,重置超时conn.process_event(disconnect{}); // 断开连接,返回 Disconnectedstd::puts("\n== Reconnect & timeout ==");conn.process_event(connect{}); // 重新连接,进入 Connectingconn.process_event(established{}); // 连接成功,进入 Connectedconn.process_event(timeout{}); // 超时断开连接,返回 Disconnectedreturn 0;
}
- 测试演示了从
Disconnected
状态开始,如何依次触发connect
,established
,ping
,disconnect
事件以及状态切换。 - 之后又进行了重新连接和超时断开连接的操作。
总结
这个代码示范了如何使用 std::variant
来管理状态机的状态,并通过 std::visit
配合多态行为来处理不同状态下的事件。overload
结构体通过合并多个 lambda
表达式,使得每个状态下的事件处理可以更加简洁易读。
来解释一下 std::visit
是如何在这个代码中使用的。
std::visit
的基本作用
std::visit
是一个模板函数,它用于访问 std::variant
中当前持有的类型,并且可以对不同的类型执行不同的操作。你可以把它看成是一个类似于 switch
的机制,根据 std::variant
当前存储的类型来选择执行不同的操作。
std::visit
的语法
std::visit(visitor, variant);
- visitor:是一个可调用对象,通常是一个 lambda 表达式或其他函数对象,它定义了在
variant
中的每个类型上应执行的操作。 - variant:是一个
std::variant
对象,它包含了多个类型中的一个。
当你调用std::visit
时,它会自动查找variant
当前包含的类型,并选择相应的操作来执行。
std::visit
和 overload
结合使用
在这段代码中,std::visit
和 overload
辅助结构体一起使用。overload
是一个自定义的结构,它允许你将多个 lambda 函数(或其他函数对象)合并到一个对象中,从而在 std::visit
调用时提供多个处理函数。
overload
的作用
overload
使得我们可以为 std::variant
中的每一种类型定义一个 lambda 函数。std::visit
将会检查 variant
当前保存的类型,并调用适当的 lambda。
我们定义的 overload
结构体可以接受多个 lambda,每个 lambda 对应 variant
中的一种类型:
template <class... Ts>
struct overload : Ts... {using Ts::operator()...;
};
这允许你在 std::visit
中使用多个 lambda,这些 lambda 会对不同类型进行匹配和处理。
代码示例:std::visit
与 overload
在 process_event
函数中,我们有这样的代码:
void process_event(connect const&) {std::visit(overload{[&](Disconnected) {establish(); // 发起建立连接动作state = Connecting{}; // 切换到 Connecting 状态std::puts("State: Connecting");},[](auto) {// 其他状态忽略 connect 事件std::puts("Ignored: connect in current state");}}, state);
}
工作流程
state
是一个std::variant<Disconnected, Connecting, Connected>
类型的变量,它表示当前的状态。std::visit
会访问state
当前保存的类型(比如Disconnected
,Connecting
或Connected
)。- 我们使用了
overload
来定义两个不同的处理分支:- 第一个 lambda 是处理
Disconnected
类型的状态:如果state
当前是Disconnected
,就执行建立连接的动作,并切换到Connecting
状态。 - 第二个 lambda 使用了
auto
类型参数,表示如果state
不是Disconnected
(即Connecting
或Connected
),则忽略connect
事件并打印"Ignored: connect in current state"
。
- 第一个 lambda 是处理
为什么 overload
结构体这么重要?
没有 overload
,如果你想为每种状态(Disconnected
, Connecting
, Connected
)定义不同的操作,你就必须编写多个 std::visit
调用,并且每个 std::visit
都会手动指定每个状态的处理逻辑。这样会非常冗长且不灵活。
通过使用 overload
,我们能够将不同状态的处理函数组合在一个地方,使得代码更加简洁和可维护。
总结
std::visit
用来访问std::variant
中的类型,并根据类型选择不同的操作。overload
结构体帮助将多个 lambda 函数合并在一起,并传递给std::visit
,以便在不同的状态下执行不同的代码。std::get_if
是另一种访问std::variant
中具体类型的方式,但std::visit
和overload
结合使用可以使代码更加简洁且易于扩展。
简单的交通信号灯状态机
我们只关注信号灯状态(红灯、绿灯)和简单的状态切换。
#include <iostream>
#include <variant>
// 状态定义
struct Red {}; // 红灯
struct Green {}; // 绿灯
// 事件定义
struct SwitchToGreen {}; // 切换到绿灯
struct SwitchToRed {}; // 切换到红灯
// 简单的信号灯状态机
class TrafficLight {
public:std::variant<Red, Green> state = Red{}; // 初始状态为红灯// 处理 SwitchToGreen 事件void process_event(SwitchToGreen const&) {std::visit(overload{[&](Red) { std::cout << "Switching to Green from Red\n"; state = Green{}; },[](auto) { std::cout << "Ignored: SwitchToGreen in current state\n"; }}, state);}// 处理 SwitchToRed 事件void process_event(SwitchToRed const&) {std::visit(overload{[&](Green) { std::cout << "Switching to Red from Green\n"; state = Red{}; },[](auto) { std::cout << "Ignored: SwitchToRed in current state\n"; }}, state);}
};
// overload 辅助结构,合并多个 lambda
template <class... Ts>
struct overload : Ts... {using Ts::operator()...;
};
template <class... Ts>
overload(Ts...) -> overload<Ts...>;
int main() {TrafficLight light;light.process_event(SwitchToGreen{}); // 红灯 -> 绿灯light.process_event(SwitchToRed{}); // 绿灯 -> 红灯return 0;
}
解释:
- 状态定义:
Red
和Green
分别表示红灯和绿灯状态。
- 事件定义:
SwitchToGreen
表示请求切换到绿灯。SwitchToRed
表示请求切换到红灯。
- TrafficLight 类:
- 通过
std::variant<Red, Green>
来表示当前信号灯的状态。 process_event
函数根据当前状态处理事件,使用std::visit
来执行不同的逻辑。
- 通过
- overload 辅助结构:
overload
用来将多个 lambda 函数合并成一个,这样可以在std::visit
中处理不同类型的状态。
输出:
Switching to Green from Red
Switching to Red from Green
总结
这个例子演示了最简单的 std::variant
和 std::visit
用法,只涉及两种状态和两种事件的处理。状态机的每次事件处理都会检查当前状态,并根据状态执行相应的转换操作。
以下是对你提供的 std::variant
的优缺点的分析以及一个简单例子的中文解释。
std::variant
的优点 (+
):
- 小巧/高效的内存占用:
std::variant
只会存储当前活动的类型,因此它比传统的面向对象多态(如使用虚函数的基类)要节省内存。例如,如果状态机当前处于某个状态,只会占用与该状态对应的数据结构的内存,而不是所有可能状态的内存。
- 状态数据结构:
- 比如,
Disconnected
、Connecting
和Connected
可以包含不同的数据结构:Disconnected
可能没有任何数据。Connecting
可以存储一个connection_id
。Connected
可以存储一个connection_id
和一个last_ping
(最后的 ping 时间戳)。
- 每个状态的数据布局不同,但
std::variant
会根据当前状态动态分配内存,只为当前活动的类型分配内存。
- 比如,
- 与
std::expected
/ 静态异常的集成:std::variant
可以很好地与std::expected
或静态异常机制集成。例如,你可以在函数中使用std::variant
返回成功或失败的结果。- 例如,像
return Error{"timeout"}
这样的错误处理模式与std::variant
很契合,它可以存储成功的类型(比如std::monostate
或其他类型)和错误的类型(比如Error
)。
std::variant
的缺点 (-
):
- 难以重用(类似
switch
/enum
):- 使用
std::variant
可能会感觉像在处理switch
或enum
,因此它的扩展性和重用性相对较差。每次使用时,通常都需要检查当前状态并执行相应的处理,这类似于枚举类型的switch
语句。 - 在某些场景中,
std::variant
可能不如其他设计模式(如状态模式)灵活,尤其是在状态较为复杂或变化较快时。
- 使用
- 只能在 Clang 中内联(Inlined):
- 这条评论意味着,某些编译器(比如 Clang)可能能够对
std::variant
进行更好的优化(如内联),但这在其他编译器中可能没有那么高效。这意味着,std::variant
在不同编译器上的性能表现可能不一致。
- 这条评论意味着,某些编译器(比如 Clang)可能能够对
std::variant
的简单例子:
以下是一个简单的例子,演示了如何使用 std::variant
来管理连接状态:
#include <iostream>
#include <variant>
#include <string>
// 定义状态
struct Disconnected {};
struct Connecting { int connection_id; }; // 连接中,存储连接ID
struct Connected { int connection_id; std::string last_ping; }; // 连接成功,存储连接ID和最后的ping时间戳
// 定义事件
struct connect {};
struct established {};
struct ping {};
struct disconnect {};
class Connection {
public:// 当前状态,初始化为 Disconnectedstd::variant<Disconnected, Connecting, Connected> state = Disconnected{}; void process_event(connect const&) {std::visit(overload{[&](Disconnected) { std::cout << "正在连接...\n"; state = Connecting{123}; // 切换到 Connecting 状态,并存储连接ID},[](auto) { std::cout << "当前状态不处理连接事件\n"; }}, state);}void process_event(established const&) {std::visit(overload{[&](Connecting& conn) {std::cout << "连接成功,连接ID:" << conn.connection_id << "\n";state = Connected{conn.connection_id, "2023-07-05T10:00:00"}; // 切换到 Connected 状态,模拟最后的ping时间},[](auto) { std::cout << "当前状态不处理建立事件\n"; }}, state);}void process_event(ping const&) {std::visit(overload{[&](Connected& conn) {std::cout << "接收到Ping,重置超时,连接ID:" << conn.connection_id << "\n";conn.last_ping = "2023-07-05T10:30:00"; // 更新最后的ping时间戳},[](auto) { std::cout << "当前状态不处理Ping事件\n"; }}, state);}void process_event(disconnect const&) {std::visit(overload{[&](auto&) {std::cout << "正在断开连接...\n";state = Disconnected{}; // 切换到 Disconnected 状态}}, state);}
};
// overload 用于将多个 lambda 函数合并,方便在 `std::visit` 中调用
template <class... Ts>
struct overload : Ts... { using Ts::operator()...; };
template <class... Ts>
overload(Ts...) -> overload<Ts...>;
int main() {Connection conn;conn.process_event(connect{}); // 从 Disconnected 状态发起连接,进入 Connectingconn.process_event(established{}); // 连接成功,进入 Connectedconn.process_event(ping{}); // 发送 ping,重置超时conn.process_event(disconnect{}); // 断开连接,返回 Disconnectedreturn 0;
}
解释:
- 状态定义:有三个状态,分别是:
Disconnected
:表示断开连接,没有任何数据。Connecting
:表示正在连接,存储一个connection_id
(连接ID)。Connected
:表示已连接,存储connection_id
和last_ping
(最后的 ping 时间戳)。
- 事件定义:我们定义了五种事件,分别是
connect
、established
、ping
、disconnect
和timeout
,每个事件对应不同的行为。 std::variant
的使用:我们使用std::variant
来存储当前的状态,只能处于其中一个状态。通过std::visit
来访问当前状态并根据事件进行状态转换。- 状态转换:
- 当从
Disconnected
状态接收到connect
事件时,切换到Connecting
状态,并打印连接信息。 - 当从
Connecting
状态接收到established
事件时,切换到Connected
状态,并打印连接成功的消息。 - 在
Connected
状态时,接收到ping
事件时,会重置超时计时,并更新最后的 ping 时间戳。 - 在任何状态下,接收到
disconnect
事件时,都会断开连接,返回Disconnected
状态。
- 当从
总结:
- 优点:
std::variant
可以高效地管理多个可能的状态,每次只存储一个状态,避免了不必要的内存开销。- 它与错误处理机制(如
std::expected
)的集成非常顺畅,适用于那些需要返回成功或失败的函数。
- 缺点:
- 类似于使用
switch
或enum
,在状态机设计中,std::variant
可能不如面向对象的多态模式灵活,特别是在需要频繁扩展状态或事件时。 - 优化(如内联)在不同编译器中的表现可能有所不同,可能不适用于所有编译器。
- 类似于使用
提供的代码片段中,展示了如何使用 C++20 协程 (coroutines) 来实现一个基于事件驱动的连接管理的状态机。这里的关键是 co_await
、co_return
和协程的使用来挂起和恢复操作。
协程和循环的实现解释
- 协程基础:
- 协程(coroutines)是 C++20 引入的一种控制流机制,它允许函数挂起(
co_await
)并在某个条件满足时恢复(co_return
)。这使得异步编程更加简洁。 - 在协程中,
co_await
可以让我们等待某个事件的发生,类似于事件驱动编程中的事件循环。
- 协程(coroutines)是 C++20 引入的一种控制流机制,它允许函数挂起(
- 代码流程:
- 这个示例实现了一个连接状态机
Connection
,它在不同的状态下等待并响应不同的事件,如connect
、established
、ping
、timeout
和disconnect
。
代码从最外层的Disconnected
状态开始,在状态变化时依次进入Connecting
和Connected
状态。
- 这个示例实现了一个连接状态机
逐行分析
auto Connection = [](auto& in) { for (;;) { // 无限循环,等待事件,初始状态为 Disconnected// 等待一个事件并获取数据if (auto [event, data] = co_await in; event == connect) { establish(); // 处理连接事件,调用建立连接的动作} }
};
- 这是协程的外层循环,用于等待事件。
co_await in
挂起当前协程,直到接收到一个事件(event
)和相关数据(data
)。- 如果事件是
connect
,则调用establish()
,表示建立连接。然后状态会转变为Connecting
。 - 如果不是
connect
事件,则会继续等待下一个事件。
for (;;) { // 进入 Connecting 状态if (auto [event, data] = co_await in; event == established) { // 如果接收到 established 事件,表示连接建立成功} end:; // 退出当前循环}
- 当事件是
established
时,表示连接成功,状态会切换到Connected
。 - 如果没有接收到
established
事件,协程会继续等待。
for (;;) { // 进入 Connected 状态switch (auto [event, data] = co_await in; event) { case ping: // 处理 ping 事件if (is_valid(data)) { reset_timeout(); // 如果 ping 数据有效,重置超时continue; // 继续等待下一个事件} case timeout: // 处理 timeout 事件establish(); // 超时后重新建立连接break;case disconnect: // 处理 disconnect 事件close(); // 关闭连接goto end; // 结束循环,跳转到 `end` 标签,退出连接状态}}
end:;
- 进入
Connected
状态后,通过switch
语句处理不同的事件:ping
:如果接收到ping
事件,并且data
有效,则重置超时计时器。timeout
:如果超时事件发生,则重新建立连接。disconnect
:如果接收到断开连接事件,则关闭连接,并跳转到end
标签,退出状态机。
总结:
- 协程的挂起与恢复:
- 使用
co_await
挂起当前协程,等待事件的发生。 - 每个状态(如
Disconnected
、Connecting
、Connected
)都有不同的事件处理逻辑。 - 通过
co_await
等待事件并根据事件执行不同的动作。
- 使用
- 状态机的实现:
- 每个状态下的事件(
connect
、established
、ping
、timeout
、disconnect
)被协程处理。 - 状态之间的转换通过事件来驱动,例如从
Disconnected
转到Connecting
,再到Connected
,最后可能回到Disconnected
。
- 每个状态下的事件(
- 使用
goto end;
:goto end;
用于跳出深层循环并结束协程。这是一种特殊的控制流方式,在某些情况下(例如需要跳出多层嵌套循环)会使用到。
优点:
- 使用协程可以让事件驱动的代码变得简洁,并且避免了传统的回调函数或状态机设计中的复杂性。
- 通过挂起协程并等待事件,它可以实现异步行为,减少 CPU 占用,直到事件发生时才恢复处理。
总结:
这是一个使用 C++ 协程的状态机实现,模拟了一个连接的状态流转过程。下面是对每个部分的详细注释:
#include <cstdio>
#include <coroutine>
#include <utility>
// 定义不同的事件类型,每个事件通过一个 constexpr 操作符返回对应的整数值
struct connect {constexpr operator int() const { return 1; }
};
struct ping {constexpr operator int() const { return 2; }
};
struct established {constexpr operator int() const { return 3; }
};
struct timeout {constexpr operator int() const { return 4; }
};
struct disconnect {constexpr operator int() const { return 5; }
};
// 定义协程的相关动作,如连接建立、关闭、重置超时等
constexpr auto establish = [] { std::puts("establish"); };
constexpr auto close = [] { std::puts("close"); };
constexpr auto is_valid = [](auto const&) { return true; };
constexpr auto reset_timeout = [] { std::puts("reset_timeout"); };
// 状态类,负责管理协程的生命周期
struct state {struct promise_type {// 返回当前协程对象state get_return_object() {return {std::coroutine_handle<promise_type>::from_promise(*this)};}// 协程开始时,使用 suspend_never 不挂起std::suspend_never initial_suspend() { return {}; }// 协程结束时,使用 suspend_always 挂起(不抛出异常,保证 noexcept)std::suspend_always final_suspend() noexcept { return {}; }// 协程正常结束时返回的值(这里并没有实际使用)template <class T>void return_value(T) {}// 如果协程有异常发生,这里是处理的地方(目前未使用)void unhandled_exception() {}};state(std::coroutine_handle<promise_type> handle) noexcept : handle_{handle} {}state(state&& other) noexcept : handle_(std::exchange(other.handle_, {})) {}~state() noexcept {if (handle_) {handle_.destroy(); // 销毁协程句柄}}
private:std::coroutine_handle<promise_type> handle_; // 协程句柄
};
// 状态机模板类,接受事件类型
template <class TEvent>
class state_machine {
public:state_machine() = default;state_machine(const state_machine&) = delete; // 禁止拷贝构造state_machine(state_machine&&) = delete; // 禁止移动构造state_machine& operator=(const state_machine&) = delete; // 禁止拷贝赋值state_machine& operator=(state_machine&&) = delete; // 禁止移动赋值// 协程需要重载 operator co_await 使其支持协程的挂起与恢复auto operator co_await() {struct {state_machine& sm;// 判断协程是否已准备好,可以立即继续auto await_ready() const noexcept -> bool { return sm.event; }// 如果协程需要挂起,这个函数会被调用auto await_suspend(std::coroutine_handle<> coroutine) noexcept {sm.coroutine = coroutine; // 将协程句柄存储return true; // 继续挂起}// 恢复协程时执行此操作auto await_resume() const noexcept {// 当协程恢复时,重置事件struct reset {TEvent& event;~reset() { event = {}; } // 离开作用域时重置事件} _{sm.event};// 返回事件和数据return std::pair{sm.event, sm.event};}} awaiter{*this};return awaiter; // 返回协程的挂起器}// 处理事件,触发协程的继续执行void process_event(const TEvent& event) {this->event = event;coroutine.resume(); // 恢复协程}
private:TEvent event{}; // 当前事件std::coroutine_handle<> coroutine{}; // 协程句柄
};
// Connection 类模拟一个连接,维护状态机的运行
class Connection {// 定义一个连接状态协程state connection() {for (;;) {// 如果事件是 connect,建立连接if (auto [event, data] = co_await sm; event == connect{}) {establish(); // 执行建立连接的操作// 进入 Connecting 状态for (;;) {// 如果事件是 established,进入已建立状态if (auto [event, data] = co_await sm; event == established{}) {// 进入 Connected 状态for (;;) {// 根据事件不同,执行不同操作switch (auto [event, data] = co_await sm; event) {case ping{}:if (is_valid(data)) reset_timeout(); // 如果 ping 成功,重置超时continue;case timeout{}:establish(); // 如果超时,重新建立连接break;case disconnect{}:close(); // 如果事件是 disconnect,关闭连接goto end; // 跳出循环,结束连接}}}}}end:;}}
public:// 处理传入的事件template <class TEvent>void process_event(const TEvent& event) {sm.process_event(event); // 调用状态机的事件处理函数}
private:state_machine<int> sm{}; // 状态机实例,使用 int 类型的事件state initial{connection()}; // 初始化连接状态
};
// 主函数模拟状态机的事件驱动流程
int main() {Connection connection{}; // 创建连接对象// 模拟不同的事件connection.process_event(connect{}); // 连接事件connection.process_event(established{}); // 已建立事件connection.process_event(ping{}); // ping 事件connection.process_event(disconnect{}); // 断开事件// 重新触发连接事件connection.process_event(connect{}); connection.process_event(established{}); connection.process_event(ping{}); return 0;
}
代码解释:
- 事件结构 (
connect
,ping
,established
,timeout
,disconnect
):- 每个事件通过一个
constexpr operator int()
返回一个唯一的整数值。这些事件在状态机中用于控制状态的流转。
- 每个事件通过一个
- 协程:
- 在 C++ 中,协程通过
co_await
和co_return
进行挂起和恢复。state_machine
类重载了operator co_await
,允许该类的实例在协程中使用。 - 事件的处理是通过协程的方式进行的,每当
process_event
被调用时,协程会恢复执行。
- 在 C++ 中,协程通过
- 状态机类 (
state_machine
):- 该类存储当前事件并管理协程的挂起和恢复。它通过
process_event
函数更新事件并恢复协程。
- 该类存储当前事件并管理协程的挂起和恢复。它通过
- Connection 类:
- 通过
connection()
协程管理连接状态流转。每个状态(如Disconnected
,Connecting
,Connected
)对应不同的协程,处理不同类型的事件。 - 使用
goto
语句来跳出多重嵌套的循环,结束连接。
- 通过
- 主函数:
- 创建一个
Connection
对象,并通过一系列事件触发状态流转,模拟了一个简化的连接生命周期。
- 创建一个
输出:
程序在事件流转过程中输出如下信息:
- “establish”:当连接建立时触发。
- “close”:当连接关闭时触发。
- “reset_timeout”:当收到
ping
时触发重置超时。
可能的改进:
- 可以更好地组织事件类型,例如使用
std::variant
或std::optional
来处理不同的事件数据类型。 - 目前的事件类型是简单的整数类型,实际应用中可能需要更复杂的事件数据结构。
这是关于在 C++ 中使用 协程 (coroutines) 构建 状态机 或 事件驱动循环 的总结。以下是对总结中的每个要点的详细解释:
COROUTINES / LOOP - 总结
(+) 使用 C++ 特性构建结构化代码
- C++ 中的协程 使得编写异步代码更加结构化,像同步代码一样易于阅读。这样你可以用更清晰的方式来处理异步任务,避免了传统回调方法所带来的混乱。
- 协程使得事件或异步任务的处理看起来像普通的同步代码,极大提高了代码的可读性和可维护性。
(+) 容易在异步和同步版本之间切换
- 使用协程,你可以很容易地在异步和同步的版本之间进行切换。
- 协程的设计使得你可以方便地将异步代码转换为同步代码,或反之。只需要替换
co_await
语句或者适当的同步原语,就能实现这个切换,灵活性高。
- 协程的设计使得你可以方便地将异步代码转换为同步代码,或反之。只需要替换
(~) 学习曲线 (不同的思维方式)
- 协程 引入了一种不同的思维方式来处理控制流和程序执行,这对于许多开发者来说可能会有一定的学习难度。
- 与传统的同步代码不同,协程允许函数在执行过程中暂停和恢复,这种机制可能比较难理解,尤其是对于那些没有接触过异步编程的开发者来说。
(~) 需要堆内存(堆内存省略/去虚拟化)
- 使用协程时,通常需要 堆内存(特别是使用
std::coroutine_handle
时),这会增加一定的性能开销。不过,C++ 编译器通常支持 堆内存省略 和 去虚拟化 来优化这一问题。- 当协程暂停或恢复时,可能会在堆上分配内存来存储协程的状态。然而,现代 C++ 编译器会通过优化技术减少这种开销。
(~) 隐式状态(函数中的位置)
- 协程 会隐式地保存函数执行的状态,也就是程序在暂停点时的执行位置,并在恢复时从这个位置继续。
- 这种隐式的状态管理虽然方便,但也使得程序的执行路径变得不那么直观,开发者可能需要仔细追踪程序的流向才能理解状态的管理。
(-) 事件需要一个共同的类型
- 在基于事件驱动的系统中,事件通常需要使用一个 共同的类型 来处理(例如
std::variant
或std::any
)。- 由于事件驱动的循环需要处理不同类型的事件,这些事件通常需要包装成一个共同的基类或者类型。这就增加了设计的复杂性。
(-) 无限循环的怪异用法
- 使用协程和事件驱动循环时,可能会出现 无限循环(例如你的状态机代码中的
for (;;)
)。- 尽管在某些事件驱动系统中,这种无限循环是必要的,但它在一些开发者看来可能比较怪异,因为它不遵循传统的
while
或for
循环那样有明确的退出条件。 - 此外,使用无限循环可能会使程序的终止逻辑变得不太明确。
- 尽管在某些事件驱动系统中,这种无限循环是必要的,但它在一些开发者看来可能比较怪异,因为它不遵循传统的
总结:
- 优点:
- 协程使得异步编程变得更加结构化,易于理解和维护。
- 它允许灵活地在同步和异步版本之间切换,提高了代码的适应性。
- 通过减少回调嵌套,协程能够简化状态机和事件驱动系统的实现。
- 缺点和挑战:
- 协程的学习曲线较陡,需要改变传统的思维方式来理解和实现异步控制流。
- 在某些情况下,协程需要进行堆内存分配,这可能带来性能开销,尽管编译器会进行优化。
- 协程的隐式状态管理(函数暂停和恢复时的位置)可能使得代码的执行路径更加难以追踪。
- 事件需要统一的类型,这可能使设计更加复杂。
- 使用无限循环可能让程序的流程看起来不太自然,导致某些开发者感觉不适应。
总结的背景:
这篇总结似乎是对在 C++ 中使用协程进行 事件驱动 或 状态机 编程的一种权衡分析。虽然协程是处理异步任务的强大工具,但它也带来了复杂性,并要求开发者从不同的角度来理解程序的控制流。对于某些场景来说,它是非常有用的,但对于初学者或更复杂的应用,它可能需要更多的关注和谨慎使用。
在这段代码中,使用了 C++ 协程 (coroutines) 与 goto
语句的结合来实现一个简单的 状态机。这个实现中的主要特点是通过 goto
来模拟状态的跳转,表示状态机中的不同状态。
代码解析:
这段代码展示了如何使用协程与 goto
语句来模拟一个连接的状态机:
1. auto Connection = [](auto& in)
这是一个 Lambda 表达式,它接受一个输入(in
)并在其中进行协程操作。输入 in
很可能是一个事件队列,协程会根据事件来决定状态机的状态和动作。
2. disconnected:
和状态表示
disconnected: // State is represented by a position in the function
goto
的使用被用来模拟状态机中的不同状态。disconnected
表示当前是“断开连接”状态。不同的标签代表不同的状态,状态之间通过goto
跳转。
3. if (auto [event, data] = co_await in; event == connect)
- 这行代码通过协程的
co_await
等待事件。当事件in
为connect
时,状态机会进入连接过程。 - 当收到
connect
事件时,它会执行establish()
,即开始建立连接。
4. connecting:
标签
connecting:
if (auto [event, data] = co_await in; event == established) {
- 如果接收到
established
事件,表示连接成功。此时,程序将跳转到connected
状态,进行进一步处理。
5. connected:
标签和 switch
语句
connected:
switch (auto [event, data] = co_await in; event) {case ping:if (is_valid(data)) { reset_timeout(); goto connected; }case timeout:establish();goto connecting; // Set the new statecase disconnect:close();goto disconnected;
}
- 在
connected
状态下,程序会根据收到的事件来决定下一步操作:ping
: 如果数据有效,调用reset_timeout()
并保持在connected
状态。timeout
: 如果发生超时,重新建立连接 (establish()
),然后转到connecting
状态。disconnect
: 如果接收到断开连接的事件,调用close()
并转到disconnected
状态。
6. goto
的使用
goto
是 C++ 中的跳转语句,它可以让你从一个位置跳转到代码中的另一位置。在这里,goto
被用来表示状态的转换。
- 比如:
- 从
connected
状态跳转到connecting
状态(即连接中状态)。 - 从
connected
状态跳转回disconnected
状态(即断开连接)。
- 从
7. 协程与 goto
的结合
协程本身支持暂停和恢复执行,而 goto
则是让程序跳到不同的地方进行执行。二者的结合使得代码能够有一个明确的执行路径,可以从一个“状态”跳到另一个状态。
总结:
这段代码实现了一个简单的 事件驱动状态机,通过协程和 goto
来处理事件与状态转换。每个状态(如 disconnected
, connecting
, connected
)都有一个标签,程序根据事件来决定是否跳转到下一个状态。
- 优点:
- 结构简洁,状态之间的切换非常明确。
goto
的使用使得状态切换逻辑简单,直接通过标签跳转。
- 缺点:
goto
的使用让代码可读性差,增加了程序的复杂度,特别是当状态机变得更复杂时,goto
会导致程序流程变得难以理解和维护。- 协程和
goto
的结合可能会让程序的执行流程显得不那么直观,尤其是在复杂的状态机中。
这种方法适合的场景:
- 对于小型和简单的状态机,
goto
可以作为一种快速实现状态转换的方式,尤其是在需要直接控制状态跳转时。
然而,随着系统复杂度的增加,可能需要更先进的技术(如 状态设计模式 或 状态模式)来替代goto
。
#include <cstdio>
#include <coroutine>
#include <utility> // 用于 std::exchange(用于交换两个对象的值)
// 定义五种事件类型,表示连接状态机中的各种状态转换
struct connect {constexpr operator int() const { return 1; } // 连接事件
};
struct ping {constexpr operator int() const { return 2; } // Ping 请求事件
};
struct established {constexpr operator int() const { return 3; } // 连接已建立事件
};
struct timeout {constexpr operator int() const { return 4; } // 超时事件
};
struct disconnect {constexpr operator int() const { return 5; } // 断开连接事件
};
// 相关事件的动作
constexpr auto establish = [] { std::puts("establish"); }; // 输出 "establish"
constexpr auto close = [] { std::puts("close"); }; // 输出 "close"
constexpr auto is_valid = [](auto const&) { return true; }; // 返回真,表示有效
constexpr auto reset_timeout = [] { std::puts("reset_timeout"); }; // 输出 "reset_timeout"
// 状态类,包含一个内部 promise_type 类型用于管理协程
struct state {struct promise_type {state get_return_object() {// 返回创建的状态对象return {std::coroutine_handle<promise_type>::from_promise(*this)};}std::suspend_never initial_suspend() {return {};} // 初始挂起,返回 std::suspend_never,表示协程一开始不挂起std::suspend_always final_suspend() noexcept { return {}; } // 最终挂起,协程结束时会挂起template <class T>void return_value(T) {} // 返回值的处理,但本例中并不使用返回值void unhandled_exception() {} // 处理未处理的异常};state(std::coroutine_handle<promise_type> handle) noexcept: handle_{handle} {} // 构造函数,接收协程句柄state(state&& other) noexcept : handle_(std::exchange(other.handle_, {})) {} // 移动构造函数~state() noexcept {if (handle_) {handle_.destroy(); // 销毁协程句柄,释放资源}}
private:std::coroutine_handle<promise_type> handle_; // 协程句柄,用于操作协程
};
// 状态机模板类,管理事件处理
template <class TEvent>
class state_machine {
public:state_machine() = default; // 默认构造函数// 删除拷贝构造函数和拷贝赋值操作符state_machine(const state_machine&) = delete;state_machine(state_machine&&) = delete;state_machine& operator=(const state_machine&) = delete;state_machine& operator=(state_machine&&) = delete;// 协程的 awaitable 操作符(co_await),用于协程的挂起与恢复auto operator co_await() {struct {state_machine& sm;// 判断是否可以挂起,事件存在时返回 trueauto await_ready() const noexcept -> bool { return sm.event; }// 协程挂起时调用,保存当前协程句柄auto await_suspend(std::coroutine_handle<> coroutine) noexcept {sm.coroutine = coroutine;return true;}// 协程恢复时调用,返回事件数据auto await_resume() const noexcept {struct reset {TEvent& event;~reset() { event = {}; } // 协程完成时重置事件} _{sm.event};return std::pair{sm.event, sm.event}; // 返回事件和数据的配对}} awaiter{*this};return awaiter;}// 处理事件并恢复协程void process_event(const TEvent& event) {this->event = event; // 更新当前事件coroutine.resume(); // 恢复协程}
private:TEvent event{}; // 事件数据std::coroutine_handle<> coroutine{}; // 当前协程的句柄
};
// 连接类,表示状态机的主体
class Connection {// 定义连接状态的协程,包含状态机处理逻辑state connection() {for (;;) {disconnected:// 处理连接事件,如果状态是 "connect",则执行建立连接操作if (auto [event, data] = co_await sm; event == connect{}) {establish();connecting:// 处理连接已建立事件if (auto [event, data] = co_await sm; event == established{}) {connected:// 根据不同事件的类型处理相应操作switch (auto [event, data] = co_await sm; event) {case ping{}:// 如果是 ping 请求,检查有效性并重置超时if (is_valid(data)) reset_timeout();goto connected; // 保持连接状态case timeout{}:// 如果是超时事件,重新建立连接establish();goto connecting; // 重新连接case disconnect{}:// 如果是断开连接事件,执行关闭操作close();goto disconnected; // 回到断开状态}}}}}
public:// 处理传入事件的接口template <class TEvent>void process_event(const TEvent& event) {sm.process_event(event); // 将事件传递给状态机处理}
private:state_machine<int> sm{}; // 创建一个类型为 int 的状态机实例state initial{connection()}; // 创建并初始化一个协程对象
};
int main() {Connection connection{}; // 创建一个 Connection 实例// 依次处理不同的事件connection.process_event(connect{}); // 处理连接事件connection.process_event(established{}); // 处理连接已建立事件connection.process_event(ping{}); // 处理 Ping 请求事件connection.process_event(disconnect{}); // 处理断开连接事件// 再次触发事件,模拟状态机的重复运行connection.process_event(connect{}); // 重新处理连接事件connection.process_event(established{}); // 重新处理连接已建立事件connection.process_event(ping{}); // 重新处理 Ping 请求事件
}
这段代码实现了一个简单的基于状态机的连接管理模型,使用了 C++20 的协程(co_await
、std::coroutine_handle
等)来处理连接状态的转移和事件的响应。以下是对代码各个部分的详细理解:
1. 事件类型
事件类型 connect
、ping
、established
、timeout
、disconnect
定义了状态机中不同的事件。每个事件都有一个 constexpr
运算符 operator int()
,返回一个整数值,代表该事件的标识。这些事件将被传递到状态机中,触发不同的状态转换。
struct connect {constexpr operator int() const { return 1; } // 连接事件
};
struct ping {constexpr operator int() const { return 2; } // Ping 请求事件
};
struct established {constexpr operator int() const { return 3; } // 连接已建立事件
};
struct timeout {constexpr operator int() const { return 4; } // 超时事件
};
struct disconnect {constexpr operator int() const { return 5; } // 断开连接事件
};
这些事件会触发不同的动作,比如建立连接、重置超时、关闭连接等。
2. 动作(Actions)
establish
、close
、is_valid
和 reset_timeout
是事件发生时需要执行的动作,它们分别表示建立连接、关闭连接、验证事件有效性和重置超时。
constexpr auto establish = [] { std::puts("establish"); };
constexpr auto close = [] { std::puts("close"); };
constexpr auto is_valid = [](auto const&) { return true; };
constexpr auto reset_timeout = [] { std::puts("reset_timeout"); };
这些动作通过 std::puts
输出信息,模拟实际的事件处理过程。
3. 状态类 (state
)
state
类负责管理协程的生命周期。它内部的 promise_type
类用于创建和管理协程句柄。get_return_object()
方法返回 state
对象,initial_suspend()
和 final_suspend()
方法分别控制协程的初始和最终挂起行为。
struct state {struct promise_type {state get_return_object() {return {std::coroutine_handle<promise_type>::from_promise(*this)};}std::suspend_never initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; }template <class T>void return_value(T) {}void unhandled_exception() {}};state(std::coroutine_handle<promise_type> handle) noexcept : handle_{handle} {}state(state&& other) noexcept : handle_(std::exchange(other.handle_, {})) {}~state() noexcept {if (handle_) {handle_.destroy(); // 销毁协程句柄,释放资源}}
private:std::coroutine_handle<promise_type> handle_; // 协程句柄
};
- 协程句柄:
std::coroutine_handle<promise_type>
用于管理协程的生命周期,允许在适当的时候销毁协程。 initial_suspend()
和final_suspend()
:控制协程开始时是否挂起以及结束时的行为。
4. 状态机 (state_machine
)
state_machine
是一个模板类,用来管理事件的处理过程。它的 operator co_await()
用于挂起协程,直到一个事件发生,然后通过 await_resume()
恢复协程的执行。
template <class TEvent>
class state_machine {
public:state_machine() = default;// 协程的 co_await 操作符,用于挂起与恢复协程auto operator co_await() {struct {state_machine& sm;// 判断是否可以挂起auto await_ready() const noexcept -> bool { return sm.event; }// 协程挂起时,保存当前协程句柄auto await_suspend(std::coroutine_handle<> coroutine) noexcept {sm.coroutine = coroutine;return true;}// 协程恢复时,返回事件数据auto await_resume() const noexcept {struct reset {TEvent& event;~reset() { event = {}; } // 重置事件} _{sm.event};return std::pair{sm.event, sm.event}; // 返回事件和数据的配对}} awaiter{*this};return awaiter;}// 处理事件并恢复协程void process_event(const TEvent& event) {this->event = event; // 更新事件coroutine.resume(); // 恢复协程}
private:TEvent event{}; // 当前事件std::coroutine_handle<> coroutine{}; // 当前协程的句柄
};
co_await
:这是协程机制的核心,当状态机挂起时,co_await
将使得当前的协程暂停,直到事件发生,之后恢复并继续执行。process_event
:这个方法用于触发事件并恢复协程。每次事件处理后,状态机会继续执行下一个状态转移。
5. 连接类 (Connection
)
Connection
类表示一个连接的状态机,通过 state_machine<int>
来管理事件流。它的核心是一个协程 connection()
,它不断地处理不同的事件并根据事件更新状态。
class Connection {state connection() {for (;;) {disconnected:// 处理连接事件if (auto [event, data] = co_await sm; event == connect{}) {establish();connecting:// 处理连接已建立事件if (auto [event, data] = co_await sm; event == established{}) {connected:switch (auto [event, data] = co_await sm; event) {case ping{}:if (is_valid(data)) reset_timeout();goto connected; // 保持连接case timeout{}:establish();goto connecting; // 重新连接case disconnect{}:close();goto disconnected; // 断开连接}}}}}
public:template <class TEvent>void process_event(const TEvent& event) {sm.process_event(event); // 处理传入事件}
private:state_machine<int> sm{}; // 状态机实例state initial{connection()}; // 初始化协程
};
- 协程逻辑:
connection()
协程处理连接的整个生命周期。通过co_await
等待事件的到来,并根据事件触发相应的动作(如建立连接、发送 Ping、断开连接等)。 goto
语句:使用goto
跳转到不同的标签来处理不同的事件。虽然goto
语句在某些情况下很有用,但在实际的代码中,使用switch
或其他结构可能会更清晰。
6. main
函数
main
函数模拟了一个简单的事件流,依次处理连接、连接已建立、Ping 请求、断开连接等事件。每个事件都通过 process_event
方法传递到状态机。
int main() {Connection connection{}; // 创建一个连接实例// 依次处理不同的事件connection.process_event(connect{}); // 处理连接事件connection.process_event(established{}); // 处理连接已建立事件connection.process_event(ping{}); // 处理 Ping 请求事件connection.process_event(disconnect{}); // 处理断开连接事件// 再次触发事件,模拟状态机的重复运行connection.process_event(connect{}); // 重新处理连接事件connection.process_event(established{}); // 重新处理连接已建立事件connection.process_event(ping{}); // 重新处理 Ping 请求事件
}
总结
- 协程:协程机制使得状态机在处理事件时更加高效与灵活。每次事件触发后,协程会挂起,等待下一个事件发生,再恢复执行。
- 状态机:
state_machine
类通过处理事件来管理连接的状态,允许状态机根据事件改变其状态。 - 事件驱动:
Connection
类根据不同的事件(如连接、Ping、超时等)执行不同的动作,使得连接的生命周期能够在不同状态间流转。
它似乎是在描述协程(coroutines)与 goto
语句结合使用时的一些优缺点,并列出了一些特性和潜在问题。以下是对每个要点的解释:
(+): No Infinite Loops (没有无限循环)
协程(coroutines)能够通过挂起与恢复机制有效地避免无限循环。每次协程挂起后,它会等待某个条件(例如事件的发生、某个状态的变化等),这意味着它不是一个传统的永远执行的循环,而是一个“暂停-继续”的过程。
- 协程:通常被设计成只有在特定条件下才会恢复,这样避免了不必要的循环,也使得资源的使用更为高效。
goto
:如果不小心处理,goto
语句可能会导致程序跳转到错误的地方,甚至可能引发无限循环的问题。然而,在状态机的上下文中,goto
可能只是为了控制流程而暂时使用,且有适当的退出条件。
(~): Explicit States (显式状态)
协程和 goto
结合使用时,程序的状态是显式管理的。每个状态(如连接、断开、超时等)都对应着明确的动作。与一般的循环或递归函数不同,协程依赖状态机和事件驱动模型,控制状态的显式转移。
- 协程:每个状态和事件的处理都会在某种“显式”状态下完成(例如
disconnected
、connecting
等)。状态和事件的转移是明确的、定义好的。 goto
:尽管goto
也可以直接跳转到不同的状态,但其跳转通常是无条件的,因此需要开发者小心避免在复杂的状态转换中使用不当的跳转,避免让状态变得隐晦不清。
(-): GOTO! (不要使用 goto
!)
这一点显然是对 goto
语句的强烈反对。goto
被认为是一种破坏程序结构清晰性的做法,特别是在复杂的程序中,goto
可能导致程序控制流难以追踪和调试。
- 协程:协程通过
co_await
和状态机的配合,使得程序的执行流程更加可控和易于理解。它们能够显式地挂起和恢复,不依赖于跳转语句,从而避免了goto
的混乱和潜在错误。 goto
的问题:goto
会让程序的控制流变得不明确,程序可能在没有足够条件的情况下跳转,从而导致难以调试和理解的代码。使用goto
会让状态之间的转换变得不清晰,这在复杂系统中尤其容易引起问题。
总结
- 协程的优势:通过显式的状态和事件驱动机制,协程使得程序的控制流更加清晰和可控,避免了无限循环,并使得状态之间的转换更加明确。
goto
的缺点:虽然goto
可以实现跳转控制,但其使用可能导致不可预测的控制流,容易出现难以调试的错误,并使代码的可读性和维护性大大降低。
从这个角度看,协程与goto
结合使用时,可能存在某些优点(如避免无限循环、明确状态),但最重要的一点是避免滥用goto
,因为它带来的代码可维护性和清晰性的问题。
这段代码展示了一个 基于协程 和 std::variant
的事件驱动的状态机。它包括了两种状态:disconnected()
和 connected()
。这两种状态通过协程的挂起与恢复来处理不同的事件,状态在不同的事件下通过 co_return
进行切换。
实现解析:
1. disconnected()
状态函数:
auto disconnected() { for (;;) { // Wait for the connect event...if (auto const event = co_await in; std::get_if<connect>(&event)) { establish(); co_return connecting(); // Set the new state}}
}
- 功能:当系统处于 “disconnected” 状态时,它会无限循环等待一个事件。在此状态下,协程等待一个
connect
事件的到来。 co_await in
:这里in
是一个事件源(例如,事件队列或状态机的输入),co_await
使得协程挂起,直到有事件发生。std::get_if<connect>(&event)
:检查收到的事件是否为connect
类型的事件。如果是,系统会调用establish()
函数来建立连接,然后通过co_return connecting()
切换到 “connecting” 状态。
这意味着,co_return
会结束当前协程并进入新的状态,即connecting()
。
2. connected()
状态函数:
auto connected() { for (;;) { // Wait for the ping event...auto const event = co_await in; if (std::get_if<ping>(&event) and is_valid(std::get<ping>(event))) { reset_timeout(); } else if (std::get_if<timeout>(&event)) { establish(); co_return connecting(); } else if (std::get_if<disconnect>(&event)) { close(); co_return disconnected(); } }
}
- 功能:当系统处于 “connected” 状态时,它会等待各种事件:
ping
、timeout
和disconnect
。 co_await in
:同样,co_await
会挂起协程,直到接收到事件。- 事件处理:
ping
:如果接收到ping
事件并且数据有效(通过is_valid
检查),系统调用reset_timeout()
来重置超时机制。timeout
:如果接收到超时事件(timeout
),系统会重新建立连接,并通过co_return connecting()
切换到 “connecting” 状态。disconnect
:如果接收到断开连接事件(disconnect
),系统调用close()
关闭连接,并通过co_return disconnected()
切换到 “disconnected” 状态。
- 状态切换:每次处理完事件后,协程会通过
co_return
切换到另一个状态,使得状态机在不同的事件下能够在多个状态之间转换。
关键点:
std::variant
和std::get_if
:- 事件(如
connect
、ping
、timeout
、disconnect
)被封装在std::variant
类型中。 std::get_if<T>
用于从std::variant
中提取特定类型的事件。只有当事件匹配特定类型时,std::get_if
才会返回有效指针。
- 事件(如
- 协程的挂起与恢复:
- 协程通过
co_await
挂起,等待事件的发生。 - 事件发生后,协程被恢复并继续执行,执行的代码决定了下一步的状态转换。
- 协程通过
- 状态机模式:
disconnected()
和connected()
分别表示断开连接和连接成功后的状态。- 每个状态使用一个独立的协程处理,不同事件触发不同的操作,协程在事件发生时恢复并切换到新状态。
- 状态切换:
- 使用
co_return
来返回下一个状态,实现状态机的转换。这是协程中状态管理的核心,状态机在不同的事件下能精确地切换。
- 使用
总结:
这个实现展示了如何使用 协程 和 std::variant
来实现一个事件驱动的 状态机。每个状态(如 disconnected
和 connected
)对应一个协程,该协程等待并处理不同的事件,通过 co_await
挂起和 co_return
返回新的状态。这样,通过协程的机制,代码结构变得非常简洁,避免了传统方法中复杂的 goto
或状态转移逻辑。
#include <cstdio>
#include <coroutine>
#include <variant>
#include <utility> // 用于 std::exchange
// 定义五种事件类型,用于描述不同的连接状态变化
struct connect {}; // 连接事件
struct ping {}; // Ping 请求事件
struct established {}; // 连接已建立事件
struct timeout {}; // 超时事件
struct disconnect {}; // 断开连接事件
// 定义一些操作函数,模拟连接的动作
constexpr auto establish = [] { std::puts("establish"); }; // 输出 "establish"
constexpr auto close = [] { std::puts("close"); }; // 输出 "close"
constexpr auto is_valid = [](auto const&) { return true; }; // 返回真,表示有效
constexpr auto reset_timeout = [] { std::puts("reset_timeout"); }; // 输出 "reset_timeout"
// `state` 类代表协程的 Promise 类型,管理协程生命周期
struct state {struct promise_type {// 获取返回对象,创建一个 `state` 实例state get_return_object() {return {std::coroutine_handle<promise_type>::from_promise(*this)};}// 初始挂起,返回 `std::suspend_never`,表示协程一开始不挂起std::suspend_never initial_suspend() { return {}; }// 最终挂起,协程结束时会挂起std::suspend_always final_suspend() noexcept { return {}; }// 返回值的处理,但本例中并不使用返回值template <class T>void return_value(T) {}// 处理未处理的异常void unhandled_exception() {}};state(std::coroutine_handle<promise_type> handle) noexcept : handle_{handle} {}// 移动构造函数state(state&& other) noexcept : handle_(std::exchange(other.handle_, {})) {}// 析构函数,销毁协程句柄~state() noexcept {if (handle_) {handle_.destroy(); // 销毁协程句柄,释放资源}}
private:std::coroutine_handle<promise_type> handle_; // 协程句柄
};
// 状态机模板,管理事件的处理
template <class... TEvents>
class state_machine {
public:state_machine() = default;// 禁止拷贝构造和拷贝赋值state_machine(const state_machine&) = delete;state_machine(state_machine&&) = delete;state_machine& operator=(const state_machine&) = delete;state_machine& operator=(state_machine&&) = delete;// `co_await` 操作符,用于协程的挂起与恢复auto operator co_await() {struct {state_machine& sm;// 判断是否可以挂起,事件存在时返回 trueauto await_ready() const noexcept -> bool { return sm.event.index(); }// 协程挂起时调用,保存当前协程句柄auto await_suspend(std::coroutine_handle<> coroutine) noexcept {sm.coroutine = coroutine;return true;}// 协程恢复时调用,返回事件数据auto await_resume() const noexcept {struct reset {std::variant<TEvents...>& event;// 协程完成时重置事件~reset() { event = {}; }} _{sm.event};return sm.event;}} awaiter{*this};return awaiter;}// 处理事件并恢复协程template <class TEvent>void process_event(const TEvent& event) {this->event = event; // 更新当前事件coroutine.resume(); // 恢复协程}
private:std::variant<TEvents...> event{}; // 存储事件数据std::coroutine_handle<> coroutine{}; // 当前协程的句柄
};
// 连接类,表示状态机的主体
class Connection {// 定义连接状态的协程,包含状态机处理逻辑state connecting() {for (;;) {// 等待已建立连接事件if (const auto event = co_await sm; std::get_if<established>(&event)) {co_return connected(); // 切换到 "connected" 状态}}}// 定义断开连接状态的协程state disconnected() {for (;;) {// 等待连接事件if (const auto event = co_await sm; std::get_if<connect>(&event)) {establish(); // 执行连接操作co_return connecting(); // 切换到 "connecting" 状态}}}// 定义已连接状态的协程state connected() {for (;;) {const auto event = co_await sm;// 处理 ping 请求事件,如果有效则重置超时if (std::get_if<ping>(&event) and is_valid(std::get<ping>(event))) {reset_timeout();}// 处理超时事件,重新建立连接并切换到 "connecting" 状态else if (std::get_if<timeout>(&event)) {establish();co_return connecting();}// 处理断开连接事件,执行关闭操作并切换到 "disconnected" 状态else if (std::get_if<disconnect>(&event)) {close();co_return disconnected();}}}
public:// 提供一个接口来处理传入的事件template <class TEvent>void process_event(const TEvent& event) {sm.process_event(event); // 将事件传递给状态机}
private:state_machine<connect, established, ping, timeout, disconnect> sm{}; // 创建状态机实例state initial{disconnected()}; // 初始状态为断开连接状态
};
// 主函数,模拟事件流
int main() {Connection connection{}; // 创建一个连接实例// 依次处理不同的事件connection.process_event(connect{}); // 处理连接事件connection.process_event(established{}); // 处理连接已建立事件connection.process_event(ping{}); // 处理 Ping 请求事件connection.process_event(disconnect{}); // 处理断开连接事件// 再次触发事件,模拟状态机的重复运行connection.process_event(connect{}); // 重新处理连接事件connection.process_event(established{}); // 重新处理连接已建立事件connection.process_event(ping{}); // 重新处理 Ping 请求事件
}
这段代码展示了如何使用 C++20 的协程 和 std::variant
实现一个事件驱动的 状态机,管理一个 连接 (Connection) 状态。接下来,我将详细注释每一部分代码,帮助理解它是如何工作的。
整体设计
- 协程 用于在不同的状态之间异步切换。
std::variant
用于存储和处理不同类型的事件。- 状态机 处理连接的生命周期,涵盖了 连接中 (connecting)、已连接 (connected) 和 断开连接 (disconnected) 三个主要状态。
代码详解与注释
#include <cstdio>
#include <coroutine>
#include <variant>
#include <utility> // 用于 std::exchange
<coroutine>
:引入 C++20 协程支持。<variant>
:使用std::variant
来存储和管理不同类型的事件。<utility>
:用于std::exchange
,用于交换对象的值。
事件类型定义
struct connect {}; // 连接事件
struct ping {}; // Ping 请求事件
struct established {}; // 连接已建立事件
struct timeout {}; // 超时事件
struct disconnect {}; // 断开连接事件
定义了五个简单的事件结构体,每个事件代表状态机的一个触发器。
操作函数
constexpr auto establish = [] { std::puts("establish"); }; // 输出 "establish"
constexpr auto close = [] { std::puts("close"); }; // 输出 "close"
constexpr auto is_valid = [](auto const&) { return true; }; // 返回真,表示有效
constexpr auto reset_timeout = [] { std::puts("reset_timeout"); }; // 输出 "reset_timeout"
establish
:模拟建立连接时输出"establish"
。close
:模拟关闭连接时输出"close"
。is_valid
:假设总是返回true
,表示事件数据是有效的。reset_timeout
:模拟超时重置时输出"reset_timeout"
。
state
类:协程的 Promise 类型
struct state {struct promise_type {state get_return_object() {return {std::coroutine_handle<promise_type>::from_promise(*this)};}std::suspend_never initial_suspend() { return {}; } // 协程开始时不挂起std::suspend_always final_suspend() noexcept { return {}; } // 协程结束时挂起template <class T>void return_value(T) {} // 不处理返回值void unhandled_exception() {} // 处理未捕获的异常};state(std::coroutine_handle<promise_type> handle) noexcept : handle_{handle} {}state(state&& other) noexcept : handle_(std::exchange(other.handle_, {})) {} // 移动构造~state() noexcept {if (handle_) handle_.destroy(); // 销毁协程句柄}
private:std::coroutine_handle<promise_type> handle_; // 协程句柄
};
state
类 包含了promise_type
,这是协程的承诺类型。它控制协程的生命周期:initial_suspend()
:协程开始时不挂起。final_suspend()
:协程结束时挂起。return_value()
:此处不处理协程返回值。unhandled_exception()
:处理异常。
- 协程句柄 (
std::coroutine_handle<promise_type> handle_
) 用于控制协程的执行。
状态机模板
template <class... TEvents>
class state_machine {
public:state_machine() = default;state_machine(const state_machine&) = delete;state_machine(state_machine&&) = delete;state_machine& operator=(const state_machine&) = delete;state_machine& operator=(state_machine&&) = delete;auto operator co_await() {struct {state_machine& sm;auto await_ready() const noexcept -> bool { return sm.event.index(); } // 判断是否准备好auto await_suspend(std::coroutine_handle<> coroutine) noexcept {sm.coroutine = coroutine; // 保存当前协程句柄return true;}auto await_resume() const noexcept {struct reset {std::variant<TEvents...>& event;~reset() { event = {}; } // 协程结束时重置事件} _{sm.event};return sm.event; // 返回事件}} awaiter{*this};return awaiter;}template <class TEvent>void process_event(const TEvent& event) {this->event = event; // 更新事件coroutine.resume(); // 恢复协程}
private:std::variant<TEvents...> event{}; // 用于存储不同事件类型std::coroutine_handle<> coroutine{}; // 协程句柄
};
state_machine
类 管理所有事件的处理。co_await
:通过协程挂起和恢复事件的处理。- 事件处理:通过
process_event()
传入事件并恢复协程执行。
Connection
类:表示状态机的实例
class Connection {state connecting() {for (;;) {if (const auto event = co_await sm; std::get_if<established>(&event)) {co_return connected(); // 切换到 "connected" 状态}}}state disconnected() {for (;;) {if (const auto event = co_await sm; std::get_if<connect>(&event)) {establish(); // 执行连接co_return connecting(); // 切换到 "connecting" 状态}}}state connected() {for (;;) {const auto event = co_await sm;if (std::get_if<ping>(&event) and is_valid(std::get<ping>(event))) {reset_timeout(); // 重置超时} else if (std::get_if<timeout>(&event)) {establish(); // 执行重新连接co_return connecting(); // 切换到 "connecting" 状态} else if (std::get_if<disconnect>(&event)) {close(); // 执行断开连接co_return disconnected(); // 切换到 "disconnected" 状态}}}
public:template <class TEvent>void process_event(const TEvent& event) {sm.process_event(event); // 传递事件给状态机}
private:state_machine<connect, established, ping, timeout, disconnect> sm{}; // 状态机实例state initial{disconnected()}; // 初始状态
};
Connection
类 实现了一个状态机,控制连接的生命周期:disconnected()
:等待connect
事件,建立连接后进入connecting
状态。connecting()
:等待established
事件,进入connected
状态。connected()
:处理ping
、timeout
和disconnect
事件,决定是否重置超时、重新连接或断开连接。
main
函数:模拟事件
int main() {Connection connection{}; // 创建一个连接实例connection.process_event(connect{}); // 触发连接事件connection.process_event(established{}); // 触发连接已建立事件connection.process_event(ping{}); // 触发 ping 请求connection.process_event(disconnect{}); // 触发断开连接事件connection.process_event(connect{}); // 再次触发连接事件connection.process_event(established{}); // 重新触发连接已建立事件connection.process_event(ping{}); // 再次触发 ping 请求
}
- 模拟连接的生命周期:首先是
connect
->established
->ping
->disconnect
,然后循环触发这些事件,模拟连接的重连和 ping 请求。
总结
这段代码实现了一个基于协程和 std::variant
的状态机模型,管理连接的不同状态。它通过协程挂起与恢复机制,在不同状态之间动态转换,从而处理异步事件。
- 协程:用于实现状态的挂起与恢复,简化了状态转换的复杂性。
std::variant
:用于存储和管理不同类型的事件,避免了繁琐的条件判断。
提供的总结要点主要讲述了协程和**std::variant
**在事件驱动的状态机中的优势。以下是每个要点的详细解释:
1. 更容易添加/跟踪新状态和行为:
- 状态转移通过协程自然地表示。例如,
disconnected
、connecting
、connected
等每个状态可以单独写成一个协程函数,这使得添加新的状态变得更加简单,只需要为新状态定义一个协程函数。 - 因为协程可以在特定点(例如等待事件发生时)将控制权交还给调用者,程序的流转变得更线性,更容易理解。每个状态都可以作为事件发生时,状态机将要执行的操作序列来表达。
- 添加新的行为(例如处理新的事件)只需要在对应的协程中添加一个新的分支,而不需要重写大量的逻辑。
2. 类型安全的事件处理:
std::variant
允许状态机以类型安全的方式处理不同类型的事件。- 比如,在传统的状态机实现中,可能通过整数或普通的枚举类型来表示各种事件(如
connect
、ping
、timeout
等),而使用std::variant
则确保每次处理的事件是具体的类型。 - 这意味着你可以确保每个事件都是特定类型(例如
connect
、ping
、disconnect
),这有助于避免使用非类型安全的解决方案可能带来的运行时错误。 - 类型安全也使得调试更容易,因为任何事件类型的错误使用都会在编译时被捕捉,而不是在程序运行时才发现。
总结:
- 添加新状态或行为更加简单,因为每个状态都以单独的协程形式存在,添加新状态时,只需编写一个新的协程。
- 类型安全的事件处理通过
std::variant
确保每个事件都能正确处理,避免了潜在的错误,同时提高了代码的安全性和可维护性。
这种方法适用于需要处理各种异步事件的系统,特别是在网络协议、交互式系统等领域,可以提供可扩展性和易于维护的状态机。