当前位置: 首页 > news >正文

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::variantstd::optional 管理状态。

3. Boost.Statechart 或 Boost.SML 等 Boost 库实现

特点:

  • Boost 提供的专门状态机库,功能强大且规范。
  • 支持状态嵌套、并行状态、历史状态、条件转移等复杂功能。
  • 写法规范,维护性和扩展性好。
  • 但学习曲线较陡,代码依赖 Boost。
  • 编译时间相对较长,代码更复杂。
    举例:
    用 Boost.Statechart 写一个状态机,状态和事件都是类,状态机框架自动帮你处理状态生命周期和转移。

总结对比

方面NaiveSTL-basedBoost.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)
我们想要满足或实现“连接”这个需求,思考的关键是:

  1. 怎样满足/实现连接需求?
    — 这是最基础的问题:功能怎么写才能达成目标。
  2. 怎样用最易读的方式实现连接?
    — 不仅实现功能,还要代码易于理解,方便别人(甚至自己)读懂。
  3. 怎样用最易维护的方式实现连接?
    — 易读是基础,维护性更进一步,代码写得清晰且结构合理,方便未来修改和扩展。
  4. 怎样用最高效的方式实现连接?
    — 在保证易读易维护的同时,尽可能提高运行效率(性能、资源利用等)。

总结:

这段话表达了设计良好软件的一般目标:不仅要“能用”,还要“好用”(可读性、可维护性),“用得快”(效率)。在实际设计和实现中,这些目标往往需要权衡和兼顾。

内容主要是在讲 统一建模语言(UML)2.5 中的 状态机(State Machine),结合之前提到的“连接(Connection)”功能,做一个形式化的建模说明。

理解要点:

  1. UML状态机(State Machine)
    UML状态机是一种用来描述系统状态和状态间转换的图形化模型。它帮助开发者以结构化方式描述对象的生命周期和行为。
  2. 版本 2.5
    你引用的UML 2.5规范 是该语言较新的官方标准,详细定义了状态机的语法和语义。
  3. 状态机与Connection(连接)功能的结合
    • 你提到的 “Feature: Connection” 表示这是状态机描述的功能模块。
    • 里面有若干“Scenario”(场景),比如“Connect”、“Disconnect”等,这类似BDD里的场景,表示不同的状态变化路径。
  4. 场景对应状态机
    • “Connect”场景对应状态机中从“无连接”状态到“已连接”状态的状态转换过程。
    • “Disconnect”场景则对应断开连接的状态转换。
    • 其他场景也可能对应其他状态和事件。

简单举例:

假设连接状态机:

  • 状态:Disconnected → Connecting → Connected → Disconnecting → Disconnected
  • 事件:请求连接、连接成功、断开请求、断开完成
  • 转换:收到连接请求时,状态从 Disconnected 变成 Connecting,连接成功后变成 Connected,等等。

总结:

你在用UML 2.5状态机规范来形式化描述连接相关的各种行为(场景),这有助于系统设计、验证和沟通。
链接中有完整的标准文档,推荐详细阅读。
以下是基于您提供的状态机图生成的 Mermaid 状态机代码,用于表示连接状态的转换(Disconnected、Connecting、Connected)。Mermaid 的状态图使用 stateDiagram-v2 语法:

connect / establish
timeout
established
disconnect
ping [is_valid] / reset_timeout
Disconnected
Connecting
Connected
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 结构会变复杂
无状态图支持不支持从配置或可视化工具中自动生成

更现代的替代方案

  1. 使用 enum class State {Disconnected, Connecting, Connected} 替代布尔值。
  2. 使用 状态转移表(如 Boost.SML)。
  3. 使用 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 + boolswitch + 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)

  • 使用 constexprswitch + 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
    • 重写对应事件的处理函数。
    • 通过调用上下文 Connectionchange_state 方法完成状态转换。
  • 上下文类 Connection
    • 持有当前状态(智能指针指向 State 基类派生对象)。
    • 所有事件传给当前状态处理。
    • 负责状态切换。

工作流程示例

  1. 开始是 Disconnected 状态
  2. 收到 connect 事件,调用 Disconnected::process_event
    • 执行动作 establish()(打印“Action: establish”)
    • 状态切换到 Connecting
  3. 收到 established 事件,调用 Connecting::process_event
    • 状态切换到 Connected
  4. 收到 ping 事件,调用 Connected::process_event
    • 调用 reset_timeout()(打印“Action: reset_timeout”)。
  5. 收到 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
  • 对于其他状态(ConnectingConnected),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 状态。
  • 对于其他状态(如 DisconnectedConnected),该事件会被忽略。
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 事件用于断开连接。如果当前状态是 ConnectingConnected,都会执行 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 状态。
  • 对于其他状态(如 DisconnectedConnecting),该事件会被忽略。

测试主函数

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::visitoverload 结合使用

在这段代码中,std::visitoverload 辅助结构体一起使用。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::visitoverload

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);
}

工作流程

  1. state 是一个 std::variant<Disconnected, Connecting, Connected> 类型的变量,它表示当前的状态。
  2. std::visit 会访问 state 当前保存的类型(比如 DisconnectedConnectingConnected)。
  3. 我们使用了 overload 来定义两个不同的处理分支:
    • 第一个 lambda 是处理 Disconnected 类型的状态:如果 state 当前是 Disconnected,就执行建立连接的动作,并切换到 Connecting 状态。
    • 第二个 lambda 使用了 auto 类型参数,表示如果 state 不是 Disconnected(即 ConnectingConnected),则忽略 connect 事件并打印 "Ignored: connect in current state"

为什么 overload 结构体这么重要?

没有 overload,如果你想为每种状态(Disconnected, Connecting, Connected)定义不同的操作,你就必须编写多个 std::visit 调用,并且每个 std::visit 都会手动指定每个状态的处理逻辑。这样会非常冗长且不灵活。
通过使用 overload,我们能够将不同状态的处理函数组合在一个地方,使得代码更加简洁和可维护。

总结

  • std::visit 用来访问 std::variant 中的类型,并根据类型选择不同的操作。
  • overload 结构体帮助将多个 lambda 函数合并在一起,并传递给 std::visit,以便在不同的状态下执行不同的代码。
  • std::get_if 是另一种访问 std::variant 中具体类型的方式,但 std::visitoverload 结合使用可以使代码更加简洁且易于扩展。

简单的交通信号灯状态机

我们只关注信号灯状态(红灯、绿灯)和简单的状态切换。

#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;
}

解释:

  1. 状态定义
    • RedGreen 分别表示红灯和绿灯状态。
  2. 事件定义
    • SwitchToGreen 表示请求切换到绿灯。
    • SwitchToRed 表示请求切换到红灯。
  3. TrafficLight 类
    • 通过 std::variant<Red, Green> 来表示当前信号灯的状态。
    • process_event 函数根据当前状态处理事件,使用 std::visit 来执行不同的逻辑。
  4. overload 辅助结构
    • overload 用来将多个 lambda 函数合并成一个,这样可以在 std::visit 中处理不同类型的状态。

输出:

Switching to Green from Red
Switching to Red from Green

总结

这个例子演示了最简单的 std::variantstd::visit 用法,只涉及两种状态和两种事件的处理。状态机的每次事件处理都会检查当前状态,并根据状态执行相应的转换操作。

以下是对你提供的 std::variant 的优缺点的分析以及一个简单例子的中文解释。

std::variant 的优点 (+)

  1. 小巧/高效的内存占用
    • std::variant 只会存储当前活动的类型,因此它比传统的面向对象多态(如使用虚函数的基类)要节省内存。例如,如果状态机当前处于某个状态,只会占用与该状态对应的数据结构的内存,而不是所有可能状态的内存。
  2. 状态数据结构
    • 比如,DisconnectedConnectingConnected 可以包含不同的数据结构:
      • Disconnected 可能没有任何数据。
      • Connecting 可以存储一个 connection_id
      • Connected 可以存储一个 connection_id 和一个 last_ping(最后的 ping 时间戳)。
    • 每个状态的数据布局不同,但 std::variant 会根据当前状态动态分配内存,只为当前活动的类型分配内存。
  3. std::expected / 静态异常的集成
    • std::variant 可以很好地与 std::expected 或静态异常机制集成。例如,你可以在函数中使用 std::variant 返回成功或失败的结果。
    • 例如,像 return Error{"timeout"} 这样的错误处理模式与 std::variant 很契合,它可以存储成功的类型(比如 std::monostate 或其他类型)和错误的类型(比如 Error)。

std::variant 的缺点 (-)

  1. 难以重用(类似 switch / enum
    • 使用 std::variant 可能会感觉像在处理 switchenum,因此它的扩展性和重用性相对较差。每次使用时,通常都需要检查当前状态并执行相应的处理,这类似于枚举类型的 switch 语句。
    • 在某些场景中,std::variant 可能不如其他设计模式(如状态模式)灵活,尤其是在状态较为复杂或变化较快时。
  2. 只能在 Clang 中内联(Inlined)
    • 这条评论意味着,某些编译器(比如 Clang)可能能够对 std::variant 进行更好的优化(如内联),但这在其他编译器中可能没有那么高效。这意味着,std::variant 在不同编译器上的性能表现可能不一致。

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;
}

解释

  1. 状态定义:有三个状态,分别是:
    • Disconnected:表示断开连接,没有任何数据。
    • Connecting:表示正在连接,存储一个 connection_id(连接ID)。
    • Connected:表示已连接,存储 connection_idlast_ping(最后的 ping 时间戳)。
  2. 事件定义:我们定义了五种事件,分别是 connectestablishedpingdisconnecttimeout,每个事件对应不同的行为。
  3. std::variant 的使用:我们使用 std::variant 来存储当前的状态,只能处于其中一个状态。通过 std::visit 来访问当前状态并根据事件进行状态转换。
  4. 状态转换
    • 当从 Disconnected 状态接收到 connect 事件时,切换到 Connecting 状态,并打印连接信息。
    • 当从 Connecting 状态接收到 established 事件时,切换到 Connected 状态,并打印连接成功的消息。
    • Connected 状态时,接收到 ping 事件时,会重置超时计时,并更新最后的 ping 时间戳。
    • 在任何状态下,接收到 disconnect 事件时,都会断开连接,返回 Disconnected 状态。

总结

  • 优点
    • std::variant 可以高效地管理多个可能的状态,每次只存储一个状态,避免了不必要的内存开销。
    • 它与错误处理机制(如 std::expected)的集成非常顺畅,适用于那些需要返回成功或失败的函数。
  • 缺点
    • 类似于使用 switchenum,在状态机设计中,std::variant 可能不如面向对象的多态模式灵活,特别是在需要频繁扩展状态或事件时。
    • 优化(如内联)在不同编译器中的表现可能有所不同,可能不适用于所有编译器。

提供的代码片段中,展示了如何使用 C++20 协程 (coroutines) 来实现一个基于事件驱动的连接管理的状态机。这里的关键是 co_awaitco_return 和协程的使用来挂起和恢复操作。

协程和循环的实现解释

  1. 协程基础
    • 协程(coroutines)是 C++20 引入的一种控制流机制,它允许函数挂起(co_await)并在某个条件满足时恢复(co_return)。这使得异步编程更加简洁。
    • 在协程中,co_await 可以让我们等待某个事件的发生,类似于事件驱动编程中的事件循环。
  2. 代码流程
    • 这个示例实现了一个连接状态机 Connection,它在不同的状态下等待并响应不同的事件,如 connectestablishedpingtimeoutdisconnect
      代码从最外层的 Disconnected 状态开始,在状态变化时依次进入 ConnectingConnected 状态。

逐行分析

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 标签,退出状态机。

总结

  1. 协程的挂起与恢复
    • 使用 co_await 挂起当前协程,等待事件的发生。
    • 每个状态(如 DisconnectedConnectingConnected)都有不同的事件处理逻辑。
    • 通过 co_await 等待事件并根据事件执行不同的动作。
  2. 状态机的实现
    • 每个状态下的事件(connectestablishedpingtimeoutdisconnect)被协程处理。
    • 状态之间的转换通过事件来驱动,例如从 Disconnected 转到 Connecting,再到 Connected,最后可能回到 Disconnected
  3. 使用 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;
}

代码解释:

  1. 事件结构 (connect, ping, established, timeout, disconnect):
    • 每个事件通过一个 constexpr operator int() 返回一个唯一的整数值。这些事件在状态机中用于控制状态的流转。
  2. 协程
    • 在 C++ 中,协程通过 co_awaitco_return 进行挂起和恢复。state_machine 类重载了 operator co_await,允许该类的实例在协程中使用。
    • 事件的处理是通过协程的方式进行的,每当 process_event 被调用时,协程会恢复执行。
  3. 状态机类 (state_machine):
    • 该类存储当前事件并管理协程的挂起和恢复。它通过 process_event 函数更新事件并恢复协程。
  4. Connection 类
    • 通过 connection() 协程管理连接状态流转。每个状态(如 Disconnected, Connecting, Connected)对应不同的协程,处理不同类型的事件。
    • 使用 goto 语句来跳出多重嵌套的循环,结束连接。
  5. 主函数
    • 创建一个 Connection 对象,并通过一系列事件触发状态流转,模拟了一个简化的连接生命周期。

输出:

程序在事件流转过程中输出如下信息:

  • “establish”:当连接建立时触发。
  • “close”:当连接关闭时触发。
  • “reset_timeout”:当收到 ping 时触发重置超时。

可能的改进:

  • 可以更好地组织事件类型,例如使用 std::variantstd::optional 来处理不同的事件数据类型。
  • 目前的事件类型是简单的整数类型,实际应用中可能需要更复杂的事件数据结构。

这是关于在 C++ 中使用 协程 (coroutines) 构建 状态机事件驱动循环 的总结。以下是对总结中的每个要点的详细解释:

COROUTINES / LOOP - 总结

(+) 使用 C++ 特性构建结构化代码
  • C++ 中的协程 使得编写异步代码更加结构化,像同步代码一样易于阅读。这样你可以用更清晰的方式来处理异步任务,避免了传统回调方法所带来的混乱。
    • 协程使得事件或异步任务的处理看起来像普通的同步代码,极大提高了代码的可读性和可维护性。
(+) 容易在异步和同步版本之间切换
  • 使用协程,你可以很容易地在异步和同步的版本之间进行切换。
    • 协程的设计使得你可以方便地将异步代码转换为同步代码,或反之。只需要替换 co_await 语句或者适当的同步原语,就能实现这个切换,灵活性高。
(~) 学习曲线 (不同的思维方式)
  • 协程 引入了一种不同的思维方式来处理控制流和程序执行,这对于许多开发者来说可能会有一定的学习难度。
    • 与传统的同步代码不同,协程允许函数在执行过程中暂停和恢复,这种机制可能比较难理解,尤其是对于那些没有接触过异步编程的开发者来说。
(~) 需要堆内存(堆内存省略/去虚拟化)
  • 使用协程时,通常需要 堆内存(特别是使用 std::coroutine_handle 时),这会增加一定的性能开销。不过,C++ 编译器通常支持 堆内存省略去虚拟化 来优化这一问题。
    • 当协程暂停或恢复时,可能会在堆上分配内存来存储协程的状态。然而,现代 C++ 编译器会通过优化技术减少这种开销。
(~) 隐式状态(函数中的位置)
  • 协程 会隐式地保存函数执行的状态,也就是程序在暂停点时的执行位置,并在恢复时从这个位置继续。
    • 这种隐式的状态管理虽然方便,但也使得程序的执行路径变得不那么直观,开发者可能需要仔细追踪程序的流向才能理解状态的管理。
(-) 事件需要一个共同的类型
  • 在基于事件驱动的系统中,事件通常需要使用一个 共同的类型 来处理(例如 std::variantstd::any)。
    • 由于事件驱动的循环需要处理不同类型的事件,这些事件通常需要包装成一个共同的基类或者类型。这就增加了设计的复杂性。
(-) 无限循环的怪异用法
  • 使用协程和事件驱动循环时,可能会出现 无限循环(例如你的状态机代码中的 for (;;))。
    • 尽管在某些事件驱动系统中,这种无限循环是必要的,但它在一些开发者看来可能比较怪异,因为它不遵循传统的 whilefor 循环那样有明确的退出条件。
    • 此外,使用无限循环可能会使程序的终止逻辑变得不太明确。

总结:

  • 优点:
    • 协程使得异步编程变得更加结构化,易于理解和维护。
    • 它允许灵活地在同步和异步版本之间切换,提高了代码的适应性。
    • 通过减少回调嵌套,协程能够简化状态机和事件驱动系统的实现。
  • 缺点和挑战:
    • 协程的学习曲线较陡,需要改变传统的思维方式来理解和实现异步控制流。
    • 在某些情况下,协程需要进行堆内存分配,这可能带来性能开销,尽管编译器会进行优化。
    • 协程的隐式状态管理(函数暂停和恢复时的位置)可能使得代码的执行路径更加难以追踪。
    • 事件需要统一的类型,这可能使设计更加复杂。
    • 使用无限循环可能让程序的流程看起来不太自然,导致某些开发者感觉不适应。

总结的背景:

这篇总结似乎是对在 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 等待事件。当事件 inconnect 时,状态机会进入连接过程。
  • 当收到 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_awaitstd::coroutine_handle 等)来处理连接状态的转移和事件的响应。以下是对代码各个部分的详细理解:

1. 事件类型

事件类型 connectpingestablishedtimeoutdisconnect 定义了状态机中不同的事件。每个事件都有一个 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)

establishcloseis_validreset_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 请求事件
}

总结

  1. 协程:协程机制使得状态机在处理事件时更加高效与灵活。每次事件触发后,协程会挂起,等待下一个事件发生,再恢复执行。
  2. 状态机state_machine 类通过处理事件来管理连接的状态,允许状态机根据事件改变其状态。
  3. 事件驱动Connection 类根据不同的事件(如连接、Ping、超时等)执行不同的动作,使得连接的生命周期能够在不同状态间流转。

它似乎是在描述协程(coroutines)与 goto 语句结合使用时的一些优缺点,并列出了一些特性和潜在问题。以下是对每个要点的解释:

(+): No Infinite Loops (没有无限循环)

协程(coroutines)能够通过挂起与恢复机制有效地避免无限循环。每次协程挂起后,它会等待某个条件(例如事件的发生、某个状态的变化等),这意味着它不是一个传统的永远执行的循环,而是一个“暂停-继续”的过程。

  • 协程:通常被设计成只有在特定条件下才会恢复,这样避免了不必要的循环,也使得资源的使用更为高效。
  • goto:如果不小心处理,goto 语句可能会导致程序跳转到错误的地方,甚至可能引发无限循环的问题。然而,在状态机的上下文中,goto 可能只是为了控制流程而暂时使用,且有适当的退出条件。

(~): Explicit States (显式状态)

协程和 goto 结合使用时,程序的状态是显式管理的。每个状态(如连接、断开、超时等)都对应着明确的动作。与一般的循环或递归函数不同,协程依赖状态机和事件驱动模型,控制状态的显式转移。

  • 协程:每个状态和事件的处理都会在某种“显式”状态下完成(例如 disconnectedconnecting 等)。状态和事件的转移是明确的、定义好的。
  • 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” 状态时,它会等待各种事件:pingtimeoutdisconnect
  • 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 切换到另一个状态,使得状态机在不同的事件下能够在多个状态之间转换。

关键点:

  1. std::variantstd::get_if
    • 事件(如 connectpingtimeoutdisconnect)被封装在 std::variant 类型中。
    • std::get_if<T> 用于从 std::variant 中提取特定类型的事件。只有当事件匹配特定类型时,std::get_if 才会返回有效指针。
  2. 协程的挂起与恢复
    • 协程通过 co_await 挂起,等待事件的发生。
    • 事件发生后,协程被恢复并继续执行,执行的代码决定了下一步的状态转换。
  3. 状态机模式
    • disconnected()connected() 分别表示断开连接和连接成功后的状态。
    • 每个状态使用一个独立的协程处理,不同事件触发不同的操作,协程在事件发生时恢复并切换到新状态。
  4. 状态切换
    • 使用 co_return 来返回下一个状态,实现状态机的转换。这是协程中状态管理的核心,状态机在不同的事件下能精确地切换。

总结:

这个实现展示了如何使用 协程std::variant 来实现一个事件驱动的 状态机。每个状态(如 disconnectedconnected)对应一个协程,该协程等待并处理不同的事件,通过 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():处理 pingtimeoutdisconnect 事件,决定是否重置超时、重新连接或断开连接。

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. 更容易添加/跟踪新状态和行为

  • 状态转移通过协程自然地表示。例如,disconnectedconnectingconnected等每个状态可以单独写成一个协程函数,这使得添加新的状态变得更加简单,只需要为新状态定义一个协程函数。
  • 因为协程可以在特定点(例如等待事件发生时)将控制权交还给调用者,程序的流转变得更线性,更容易理解。每个状态都可以作为事件发生时,状态机将要执行的操作序列来表达。
  • 添加新的行为(例如处理新的事件)只需要在对应的协程中添加一个新的分支,而不需要重写大量的逻辑。

2. 类型安全的事件处理

  • std::variant允许状态机以类型安全的方式处理不同类型的事件。
  • 比如,在传统的状态机实现中,可能通过整数或普通的枚举类型来表示各种事件(如connectpingtimeout等),而使用std::variant则确保每次处理的事件是具体的类型。
  • 这意味着你可以确保每个事件都是特定类型(例如connectpingdisconnect),这有助于避免使用非类型安全的解决方案可能带来的运行时错误
  • 类型安全也使得调试更容易,因为任何事件类型的错误使用都会在编译时被捕捉,而不是在程序运行时才发现。

总结

  • 添加新状态或行为更加简单,因为每个状态都以单独的协程形式存在,添加新状态时,只需编写一个新的协程。
  • 类型安全的事件处理通过std::variant确保每个事件都能正确处理,避免了潜在的错误,同时提高了代码的安全性和可维护性。
    这种方法适用于需要处理各种异步事件的系统,特别是在网络协议、交互式系统等领域,可以提供可扩展性易于维护的状态机

后面是BOOST实现的看不下去

http://www.xdnf.cn/news/1082629.html

相关文章:

  • python实现简单的地图绘制与标记20250705
  • 智链万物:人工智能驱动的产业智能化革命
  • RocketMQ面试题
  • React Hooks全面解析:从基础到高级的实用指南
  • 『 C++入門到放棄 』- string
  • Python关键字梳理
  • 【MySQL进阶】错误日志,二进制日志,mysql系统库
  • React Native 开发环境搭建--mac--android--奔溃的一天
  • virtualbox+vagrant私有网络宿主机无法ping通虚拟机问题请教
  • Java创建型模式---单例模式
  • 如何在idea里快速地切换Windows CMD、git bash、powershell
  • Spring boot之身份验证和访问控制
  • 人工智能学习70-Yolo损失函数
  • Ubuntu:Mysql服务器
  • 08_容器化与微服务:构建弹性架构
  • 【Linux】自旋锁和读写锁
  • (LeetCode 面试经典 150 题) 14. 最长公共前缀 (字符串)
  • JVM与JMM
  • 全素山药开发指南:从防痒处理到高可用食谱架构
  • 虚拟机网络编译器还原默认设置后VMnet8和VMnet1消失了
  • 2025最新软件测试面试八股文
  • WPF学习笔记(24)命令与ICommand
  • 【Oracle专栏】分区表增加分区
  • 【机器学习深度学习】模型参数量、微调效率和硬件资源的平衡点
  • Linux:多线程---深入互斥浅谈同步
  • vue中添加原生右键菜单
  • LucidShape 2024.09 最新
  • FreeCAD傻瓜教程-拉簧拉力弹簧的画法及草图的附着位置设定和Part工作台中形体构建器的妙用
  • Flutter 使用http库获取网络数据的方法(一)
  • 初识Linux:Linux开发工具gcc/g++和gdb以及Makefile的使用