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

CppCon 2018 学习:EFFECTIVE REPLACEMENT OF DYNAMIC POLYMORPHISM WITH std::variant

“Effective Replacement of Dynamic Polymorphism with std::variant 是现代 C++ 中一个重要的设计思路:用静态类型机制(如 std::variant)来替代传统的虚函数(动态多态),从而实现 更安全、更高效、更易维护的多态行为
下面我来详细讲解这个设计理念、适用场景、优缺点、和对比。

一、传统的动态多态(virtual)

struct Animal {virtual void speak() const = 0;virtual ~Animal() = default;
};
struct Dog : Animal {void speak() const override { std::cout << "Woof\n"; }
};
struct Cat : Animal {void speak() const override { std::cout << "Meow\n"; }
};
void make_sound(const Animal& a) {a.speak(); // 运行时绑定
}
  • 使用虚函数表(vtable)+ 指针/引用
  • 多态是运行时决定的
  • 灵活,但有以下缺点
    • 需要堆分配(new)
    • 无法进行 switch 分支匹配
    • 性能有虚调用开销
    • 类型不透明,不利于优化和序列化

二、替代方案:用 std::variant 代替虚函数

#include <variant>
#include <iostream>
struct Dog {void speak() const { std::cout << "Woof\n"; }
};
struct Cat {void speak() const { std::cout << "Meow\n"; }
};
using Animal = std::variant<Dog, Cat>;
void make_sound(const Animal& a) {std::visit([](const auto& animal) {animal.speak(); // 编译期就确定}, a);
}

特点

  • 没有 vtable → 不需要虚函数、继承、多态指针
  • 所有类型都在编译期已知
  • std::visit 调用对应的行为(称为“静态多态”)
  • 结构更扁平、可组合性更好
  • 性能更优:无虚调用、无堆分配

三、适用场景对比

场景virtual(动态多态)std::variant(静态多态)
类型不固定、来自外部插件
类型组合有限、已知
高性能(如游戏 ECS)
面向开放层次结构(Open/Closed)
模型具有生命周期多态行为(需要共享接口)需额外设计
模型简单、可枚举类型非常适合

四、关键点总结

方面virtualstd::variant
多态类型可扩展固定集合
类型识别RTTI / dynamic_cast编译期类型 (std::visit)
性能虚函数调度编译期分发,高效
所需语法基类 + 指针std::variant + lambda
类型安全低(运行期错误)高(编译期检查)

举例:状态机、消息、几何图元等典型适合 variant 场景

struct Circle { double r; };
struct Square { double side; };
struct Triangle { double base, height; };
using Shape = std::variant<Circle, Square, Triangle>;
double area(const Shape& shape) {return std::visit(overload{[](const Circle& c) { return 3.14 * c.r * c.r; },[](const Square& s) { return s.side * s.side; },[](const Triangle& t) { return 0.5 * t.base * t.height; }}, shape);
}

这里你完全不需要 virtual、基类、指针,且:

  • 所有 case 都由编译器检查
  • 新加类型必须修改 variant 类型 → 类型封闭(Closed set)
  • 高效,无虚调度开销

总结一句话:

如果你的多态对象集合是封闭的(固定的几种类型),并且你需要类型安全、高性能、无堆分配的代码 —— 那么使用 std::variant 替代 virtual 是更“Modern C++”的做法。

这个例子展示的是 传统的动态多态(dynamic polymorphism) 使用方式。我们逐步来解释其工作机制,以及如何能被现代 C++ 特性(如 std::variant)替代。

原始代码解释

class base : noncopyable {
public:virtual ~base() = default;virtual void foo() = 0;
};
class x : public base {
public:void foo() override { std::cout << "x\n"; }
};
class y : public base {
public:void foo() override { std::cout << "y\n"; }
};
std::unique_ptr<base> b = std::make_unique<x>();
b->foo();  // 输出:x

解释:

  • base 是一个抽象基类,带有纯虚函数 foo()
  • xy 是派生类,实现了 foo()
  • b 是一个 std::unique_ptr<base>,指向派生类对象 x
  • b->foo() 是通过虚函数表(vtable)实现的运行时多态。

什么是动态多态?

  • 基类指针(或引用)指向派生类对象
  • 调用虚函数时,由 运行时 决定调用哪一个版本
  • 通过 vtable 和 vptr 实现
  • 典型用途:插件系统、框架扩展点、接口调用

动态多态的缺点

  • 必须通过指针/引用调用(不能值语义)
  • 会失去具体类型信息(type erasure)
  • 不能在 switch 等结构中分支处理
  • 虚函数调用有轻微的性能开销
  • 无法序列化多态对象(类型信息不透明)

使用 std::variant 替代动态多态

当你知道有哪些派生类,推荐使用 std::variant 来代替:

#include <variant>
#include <iostream>
struct x {void foo() { std::cout << "x\n"; }
};
struct y {void foo() { std::cout << "y\n"; }
};
using variant_base = std::variant<x, y>;
void call_foo(variant_base& v) {std::visit([](auto& obj) {obj.foo();}, v);
}
int main() {variant_base v = x{};call_foo(v);  // 输出:xv = y{};call_foo(v);  // 输出:y
}

动态多态 vs std::variant 对比

特性virtual 方式std::variant 方式
运行时分发(编译期分发)
类型开放性可添加子类类型固定
性能稍慢(虚调用)更快(静态分发)
类型安全低(容易用错)高(编译器检查)
使用方式指针 / 引用值类型
支持访问成员不方便(需 RTTI)std::visit 统一访问

总结:如何选择?

  • 如果你需要 扩展性、运行时加载插件、动态行为 → 用 virtual(动态多态)
  • 如果你有 已知的固定类型集合,想要 高性能、类型安全 → 用 std::variant(静态多态)

进阶思考:怎么改写你给的代码为 std::variant

你原始代码:

std::unique_ptr<base> b = std::make_unique<x>();
b->foo();

用现代方式替换为:

std::variant<x, y> b = x{};
std::visit([](auto& obj) { obj.foo(); }, b);

即可获得同样的行为,而无需虚函数。

如何用 std::variant 有效替代动态多态(dynamic polymorphism)

原始动态多态方式(使用虚函数)

class base {
public:virtual ~base() = default;virtual void foo() = 0;
};
class x : public base {
public:void foo() override { std::cout << "x\n"; }
};
class y : public base {
public:void foo() override { std::cout << "y\n"; }
};
std::unique_ptr<base> b = std::make_unique<x>();
b->foo();  // 输出 x

特点:

  • 运行时决定调用哪个 foo()(虚函数 + vtable)
  • 必须通过指针/引用访问
  • 只能使用继承层级下的类(base 的派生类)

替代方式:使用 std::variant + std::visit

#include <variant>
#include <iostream>
struct x {void foo() { std::cout << "x\n"; }
};
struct y {void foo() { std::cout << "y\n"; }
};
int main() {std::variant<x, y> b = x{};std::visit([](auto&& v) { v.foo(); }, b);  // 输出 xb = y{};std::visit([](auto&& v) { v.foo(); }, b);  // 输出 y
}

为什么 std::variant 是动态多态的“有效替代”?

特性虚函数 (base*)std::variant
运行时多态(通过 visit()
编译时类型检查
值语义(指针)(直接存储对象)
速度较慢(虚函数调用)更快(inline 展开)
类无继承关系(duck typing)
内存布局稳定(heap 分配)(固定大小,无 heap)

关键点解析

1. duck typing(鸭子类型)

  • 不需要共同的基类
  • 只要有 foo() 方法,就可以放进 std::variant
struct apple { void foo(); };
struct orange { void foo(); };
// apple 和 orange 没有继承关系,但 std::variant 可以接受

2. 更快、更短

  • 没有虚函数调用的间接跳转
  • 编译器可优化 std::visit()(如内联)
  • 对象直接嵌入在 variant 里,无需 new

3. 适用范围

适合

  • 所有“行为一致”的类
  • 固定的类型集合
  • 性能敏感、无需运行时扩展
    不适合
  • 插件系统(运行时扩展)
  • 不确定的类型集合
  • 必须用接口隐藏细节时

例子总结(对比)

动态多态方式:

std::unique_ptr<base> b = std::make_unique<x>();
b->foo();

std::variant 方式:

std::variant<x, y> b = x{};
std::visit([](auto&& v){ v.foo(); }, b);

总结一句话:

当你已知类型集合,且只需要它们有相同行为(比如 foo()),

那么 std::variant + std::visit 是比传统继承更 现代、更安全、更高效 的选择。

我们来一步一步深入理解什么是 有限状态机(FSM, Finite State Machine)

基本定义(你已经说得对了)

A Finite State Machine 是一种抽象机器

它在任何时刻都只处于有限数量的状态之一

用一句话理解 FSM:

“一个系统根据输入,在状态之间切换

并在每个状态中做出不同的行为。”

构成要素(FSM 的五个核心部分):

  1. States(状态):机器可以处于的不同情境,例如:Idle, Playing, Paused
  2. Events / Inputs(事件/输入):触发状态变化的动作,例如:PlayPressed, PausePressed
  3. Transitions(转移):某个输入触发从一个状态跳转到另一个状态。
  4. Initial state(初始状态):启动时的状态。
  5. Actions(动作):进入状态时或转换时执行的逻辑代码。

现实例子:音频播放器

一个简单音乐播放器的状态机:

  • 状态(States):
    • Stopped
    • Playing
    • Paused
  • 事件(Events):
    • PlayPressed
    • PausePressed
    • StopPressed
  • 状态转换表(Transition Table):
    | Current State | Input | Next State |
    | ------------- | ------------ | ---------- |
    | Stopped | PlayPressed | Playing |
    | Playing | PausePressed | Paused |
    | Playing | StopPressed | Stopped |
    | Paused | PlayPressed | Playing |
    | Paused | StopPressed | Stopped |

状态图(State Diagram)

  [Stopped]|PlayPressed↓[Playing] <--> [Paused]↑  ↓         ↑   ↓StopPressed    Play  Stop↓[Stopped]

C++中实现一个 FSM(简化版)

#include <iostream>
#include <string>
enum class State { Stopped, Playing, Paused };
void handle_event(State& state, const std::string& event) {if (state == State::Stopped && event == "play") {state = State::Playing;std::cout << "Now Playing\n";} else if (state == State::Playing && event == "pause") {state = State::Paused;std::cout << "Paused\n";} else if (state == State::Playing && event == "stop") {state = State::Stopped;std::cout << "Stopped\n";} else if (state == State::Paused && event == "play") {state = State::Playing;std::cout << "Resuming\n";} else if (state == State::Paused && event == "stop") {state = State::Stopped;std::cout << "Stopped\n";} else {std::cout << "Invalid action in current state\n";}
}
int main() {State current = State::Stopped;handle_event(current, "play");   // Now Playinghandle_event(current, "pause");  // Pausedhandle_event(current, "play");   // Resuminghandle_event(current, "stop");   // Stopped
}

FSM 在工程中的常见用途:

场景示例
UI 逻辑按钮状态(hovered, pressed, disabled)
游戏玩家状态(走、跳、攻击)
网络协议TCP 状态(SYN_SENT, ESTABLISHED, etc.)
工业控制机器状态(加热中、冷却中、完成)

总结一句话:

有限状态机就是一个状态 + 事件驱动的行为模型,

可以让复杂系统的逻辑更清晰、更可控、更可维护

connect
connected
timeout [n = n_max]
disconnect
timeout [n < n_max]
state_idle
state_connecting
state_connected

mermaid:

stateDiagram-v2[*] --> state_idlestate_idle --> state_connecting : connectstate_connecting --> state_connected : connectedstate_connecting --> state_idle : timeout [n = n_max]state_connected --> state_connecting : disconnectstate_connecting --> state_connecting : timeout [n < n_max]

提供的是一个 FSM(有限状态机) 的状态图(用 Mermaid 的 stateDiagram-v2 语法),并附有定义性描述。我们来一步步分析并理解这张图和对应的概念。

首先,什么是 FSM?

你的总结已经非常准确:

“状态机在响应外部输入(称为事件)时改变状态,这种状态的转换称为 transition。FSM 包括状态集合、初始状态、每个 transition 的条件。”

分析你的 FSM 状态图

connect
connected
timeout [n = n_max]
disconnect
timeout [n < n_max]
state_idle
state_connecting
state_connected

各个组成部分解释:

状态(States):

  • state_idle:空闲状态
  • state_connecting:正在连接
  • state_connected:已连接

初始状态(Initial State):

  • [ * ] --> state_idle
    表示 FSM 启动时的状态是 state_idle

事件驱动的状态转换(Transitions):

FromToTrigger/Event (with condition)
[*]state_idle初始状态
state_idlestate_connectingconnect 事件
state_connectingstate_connectedconnected 事件(连接成功)
state_connectingstate_idletimeoutn == n_max
state_connectingstate_connectingtimeoutn < n_max(重试)
state_connectedstate_connectingdisconnect 事件(主动断开)

条件(Guards):

这些是“是否允许转换”的条件判断:

  • timeout [n = n_max]:如果超时并且重试次数到达上限 → 回到 idle
  • timeout [n < n_max]:如果超时但还有机会 → 继续留在 connecting 重试

完整 FSM 特征(你的模型完美覆盖了):

要素说明
状态(States)idle, connecting, connected
初始状态idle
输入事件(Events)connect, connected, disconnect, timeout
转换(Transitions)条件触发的状态变更
动作(Actions)可加在 transition 时执行,例如 on timeout: n++

对应的 C++ 思路(伪代码):

enum class State { Idle, Connecting, Connected };
int retry_count = 0;
const int max_retries = 3;
State current = State::Idle;
void handle_event(std::string event) {switch (current) {case State::Idle:if (event == "connect") {current = State::Connecting;}break;case State::Connecting:if (event == "connected") {current = State::Connected;} else if (event == "timeout") {if (retry_count >= max_retries) {current = State::Idle;} else {retry_count++;// stay in Connecting}}break;case State::Connected:if (event == "disconnect") {current = State::Connecting;}break;}
}

小结:

你对 FSM 的定义和使用理解非常清晰:

  • 有限状态集
  • 事件驱动转换
  • 状态转移包含条件判断(guard)
  • 适用于连接状态、UI 控件、游戏角色、网络协议等场景

CASE #1 SINGLE DYNAMIC DISPATCH

event
+connect
+connected
+disconnect
+timeout
fsm
+dispatch(event)
state
+on_event(event)
state_idle
state_connecting
state_connected

mermaid:

classDiagramclass event {+connect+connected+disconnect+timeout}class fsm {+dispatch(event)}class state {+on_event(event) : 0}class state_idleclass state_connectingclass state_connectedfsm o--> statestate o--> state_idlestate o--> state_connectingstate o--> state_connected

《用 std::variant 替代动态多态(dynamic polymorphism)》的早期示例,使用传统方式实现了一个 单动态派发(Single Dynamic Dispatch) 的有限状态机(FSM)模型。

目标:

构建一个通用 FSM 模板,基于事件 Event,通过虚函数实现状态转移。

代码+注释+分析

// 声明状态接口,基于事件类型进行参数化
template<typename Event>
class state : noncopyable { // 非拷贝类,继承非拷贝性
public:virtual ~state() = default;// 接收事件,返回新状态(可能是nullptr,表示无状态转换)virtual std::unique_ptr<state> on_event(Event) = 0;
};

分析:

  • 使用 virtual 实现 动态派发
  • on_event() 是核心:接收到一个 Event,返回新的 state(通过多态切换状态)。
  • 返回 nullptr → 状态不变。
template<typename Event>
class fsm {std::unique_ptr<state<Event>> state_;  // 当前状态对象
public:// 构造时设置初始状态explicit fsm(std::unique_ptr<state<Event>> state): state_(std::move(state)) {}// 派发事件,可能引发状态转移void dispatch(Event e) {auto new_state = state_->on_event(e);  // 调用虚函数,根据事件生成下一个状态if (new_state) {state_ = std::move(new_state);    // 如果有新状态,切换当前状态}// 否则保持原状态}
};

分析:

  • fsm::dispatch() 是事件驱动的入口。
  • on_event(e) 动态绑定,调用当前状态的处理逻辑。
  • 若返回新状态指针,则状态切换;否则保持不变。
  • 使用 std::unique_ptr 实现资源管理和所有权转移。

示例用途(假设):

我们可以这样构造具体状态:

struct event { int id; };
class idle_state : public state<event> {
public:std::unique_ptr<state<event>> on_event(event e) override {if (e.id == 1) {return std::make_unique<active_state>();}return nullptr;}
};
class active_state : public state<event> {
public:std::unique_ptr<state<event>> on_event(event e) override {if (e.id == 0) {return std::make_unique<idle_state>();}return nullptr;}
};

用法:

fsm<event> machine{std::make_unique<idle_state>()};
machine.dispatch({1}); // 转为 active_state
machine.dispatch({0}); // 回到 idle_state

结构图(简化):

fsm<event>
└── state<Event>├── idle_state└── active_state

每个状态类实现 on_event(),决定如何切换。

存在的问题:

  • 每个状态对象都需要堆分配(std::unique_ptr),有性能和生命周期开销。
  • 虚函数调用不利于内联,无法做大量编译时优化。
  • 不支持多态内联、多事件批处理等优化。

后续改进(CppCon 2018 提议):

std::variant<State1, State2, ...> 替代 std::unique_ptr<base>

  • 静态类型已知
  • 避免虚函数
  • 内联优化
  • 编译期检查状态合法性

小结:

优点缺点
简洁清晰,结构化强运行时开销较高(虚函数 + 堆分配)
经典 OOP 方式易于理解和扩展性能瓶颈、类型不安全(无法静态保证合法性)

这段代码是一个使用动态多态(virtual)实现的有限状态机(FSM),模拟了一个 connection_fsm(连接状态机)。状态之间通过接收到的事件(如 connect, connected, timeout, disconnect)进行转换。

整体结构

event ──> on_event(Event) ──> new state(或 nullptr)
状态:
- state_idle
- state_connecting
- state_connected
事件:
- connect
- connected
- timeout
- disconnect

完整代码 + 注释 + 分析

#include <iostream>
#include <memory>
enum class event {connect,connected,timeout,disconnect
};

定义事件类型:event 是 FSM 的输入。

// 抽象状态基类
template<typename Event>
class state {
public:virtual ~state() = default;virtual std::unique_ptr<state> on_event(Event) = 0;
};

这是所有状态的基类。每个状态响应 Event 类型的输入,返回新的状态(或不变,返回 nullptr)。

// 有限状态机类
template<typename Event>
class fsm {std::unique_ptr<state<Event>> state_;
public:explicit fsm(std::unique_ptr<state<Event>> state): state_(std::move(state)) {}void dispatch(Event e) {auto new_state = state_->on_event(e);if (new_state) {state_ = std::move(new_state);}}
};
  • fsm::dispatch():将事件传入当前状态处理。
  • 状态返回新的状态(指针),由 FSM 持有并替换旧状态。
using s = state<event>; // 简化写法

state<event> 是我们 FSM 中所有状态的抽象基类,s 是其别名。

具体状态类

class state_idle final : public s {
public:std::unique_ptr<s> on_event(event e) override;
};
class state_connecting final : public s {static constexpr int n_max = 3;int n = 0;  // 超时计数器
public:std::unique_ptr<s> on_event(event e) override;
};
class state_connected final : public s {
public:std::unique_ptr<s> on_event(event e) override;
};
  • state_idle: 等待连接命令。
  • state_connecting: 正在尝试连接,允许一定次数重试。
  • state_connected: 成功建立连接。

事件处理实现(核心逻辑)

state_idle 的响应逻辑

std::unique_ptr<s> state_idle::on_event(event e) {if (e == event::connect) {std::cout << "Transition: idle -> connecting\n";return std::make_unique<state_connecting>();}return nullptr; // 保持 idle
}
  • 如果收到 connect,转入 connecting 状态。
  • 否则状态不变。

state_connecting 的响应逻辑

std::unique_ptr<s> state_connecting::on_event(event e) {switch (e) {case event::connected:std::cout << "Transition: connecting -> connected\n";return std::make_unique<state_connected>();case event::timeout:++n;std::cout << "Timeout count: " << n << "\n";if (n < n_max) {return nullptr; // 继续等待} else {std::cout << "Max timeouts reached. Back to idle\n";return std::make_unique<state_idle>();}default:return nullptr; // 不响应其他事件}
}
  • connected: 转到 connected 状态。
  • timeout: 如果次数少于最大值 n_max,继续等待;否则回到 idle
  • 其他事件无效。

state_connected 的响应逻辑

std::unique_ptr<s> state_connected::on_event(event e) {if (e == event::disconnect) {std::cout << "Transition: connected -> idle\n";return std::make_unique<state_idle>();}return nullptr; // 保持连接状态
}
  • disconnect: 回到 idle
  • 其他事件忽略。

包装为 FSM 类

class connection_fsm : public fsm<event> {
public:connection_fsm(): fsm<event>(std::make_unique<state_idle>()) {}
};

connection_fsm 是用户接口类,初始状态为 idle

用法示例(添加主函数)

int main() {connection_fsm conn;conn.dispatch(event::connect);    // idle -> connectingconn.dispatch(event::timeout);    // retry 1conn.dispatch(event::timeout);    // retry 2conn.dispatch(event::timeout);    // retry 3, back to idleconn.dispatch(event::connect);    // idle -> connectingconn.dispatch(event::connected);  // connecting -> connectedconn.dispatch(event::disconnect); // connected -> idle
}

总结

元素功能
state<T>抽象基类,定义事件响应
fsm<T>状态持有与管理
state_idle等待连接
state_connecting连接中,处理超时重试
state_connected已连接状态,等待断开
dispatch()将事件发送给当前状态,可能产生状态切换

std::variant 替代传统动态多态** 的内容。现在我们来深入理解这部分讲述的 Single Dynamic Dispatch(单一动态分发) 特点及测试方式:

测试状态转换(Testing Transitions)

template<typename Fsm, typename... Events>
void dispatch(Fsm& fsm, Events... events) {(fsm.dispatch(events), ...); // C++17 左折叠表达式
}

解释:

这是一个变参模板函数,接受任意数量的 events 并将它们逐个传递给 FSM 的 dispatch() 方法。

(fsm.dispatch(events), ...);

等价于:

fsm.dispatch(event1);
fsm.dispatch(event2);
fsm.dispatch(event3);
// ...

使用 C++17 的fold expression 实现,简洁且强大,避免了手动循环或展开。

示例用法:

connection_fsm fsm;
dispatch(fsm, event::connect, event::timeout, event::connected, event::disconnect);

输出示意(如果你在状态转换中有打印语句):

Transition: idle -> connecting
Timeout count: 1
Transition: connecting -> connected
Transition: connected -> idle

这就是状态转换流程的测试输出。

Single Dynamic Dispatch 的优缺点分析

特性含义
Open to new alternatives用户可以随时扩展新的 state 类型,不需要修改 base 类。例如 state_paused
Closed to new operations不能后期添加新方法(只能扩展 on_event())。要加其他逻辑,得改基类。
Multi-level支持多层继承,适合复杂状态继承体系。
Object-oriented一切围绕类和虚函数运行,符合经典面向对象设计。

为何要换成 std::variant

因为:

动态多态 virtual替代方案 std::variant(静态多态)
运行时开销(虚表,堆分配)编译期决策(无虚表,栈上对象)
状态操作不可扩展(只能写 on_eventstd::visit 可自由定义操作
易出错(忘记实现虚函数时无编译报错)静态类型检查更安全
代码分散,状态间联系弱所有状态集中定义,清晰一致

总结理解

  • dispatch(fsm, ...) 用来简洁测试多个状态转换路径。
  • 单一动态分发的实现是面向对象经典做法,但不利于未来操作的扩展。
  • 你可以继续使用这种结构,或者尝试将状态机重构为 std::variant + std::visit 的形式,进一步获得性能和类型安全优势。

以下是将传统的**动态多态 FSM(有限状态机)**实现,完整地重构为使用 std::variantstd::visit静态多态版本(无虚函数,无堆分配)

std::variant 版本的 FSM 完整代码

#include <iostream>
#include <variant>
// 定义事件类型
enum class event { connect, connected, timeout, disconnect };
// 1. 前向声明状态类
struct state_idle;
struct state_connecting;
struct state_connected;
// 2. 定义状态的变体类型,代表所有可能的状态之一
using state = std::variant<state_idle, state_connecting, state_connected>;
// 3. 定义各状态结构体,并声明它们的事件处理函数
struct state_idle {// 事件处理函数,接收事件和超时计数,返回新的状态state on_event(event e, int& n) const;
};
struct state_connecting {state on_event(event e, int& n) const;
};
struct state_connected {state on_event(event e, int& n) const;
};
// 4. 实现状态的事件处理函数
// idle状态处理事件
state state_idle::on_event(event e, int&) const {if (e == event::connect) {  // 收到 connect 事件,状态转为 connectingstd::cout << "idle -> connecting\n";return state_connecting{};}return *this;  // 其他事件状态不变
}
// connecting状态处理事件
state state_connecting::on_event(event e, int& n) const {switch (e) {case event::connected:  // 收到 connected 事件,状态转为 connected,重置计数器std::cout << "connecting -> connected\n";n = 0;return state_connected{};case event::timeout:  // 收到 timeout 事件,计数器+1,3次超时后回到 idleif (++n >= 3) {std::cout << "connecting -> idle (timeout)\n";n = 0;return state_idle{};}std::cout << "connecting: timeout #" << n << "\n";return *this;  // 未达到超时次数,保持当前状态default:return *this;  // 其他事件保持状态不变}
}
// connected状态处理事件
state state_connected::on_event(event e, int&) const {if (e == event::disconnect) {  // 收到 disconnect 事件,状态转为 idlestd::cout << "connected -> idle\n";return state_idle{};}return *this;  // 其他事件状态不变
}
// 状态机定义
struct connection_fsm {state current;          // 当前状态,存储为variantint timeout_count = 0;  // 超时计数器,连接中状态使用// 派发事件,调用当前状态的 on_event 函数并更新状态void dispatch(event e) {current = std::visit([e, this](auto&& s) -> state { return s.on_event(e, timeout_count); },current);}
};
// 辅助函数:批量派发多个事件,利用 fold expression
template <typename Fsm, typename... Events>
void dispatch(Fsm& fsm, Events... events) {(fsm.dispatch(events), ...);
}
// 主函数测试状态机
int main() {connection_fsm fsm{state_idle{}};  // 初始状态为 idledispatch(fsm,event::connect,    // idle -> connectingevent::timeout,    // timeout #1event::timeout,    // timeout #2event::timeout,    // timeout #3 -> idle (超时重置)event::connect,    // idle -> connectingevent::connected,  // connecting -> connectedevent::disconnect  // connected -> idle);
}

输出结果示例:

idle -> connecting
connecting: timeout #1
connecting: timeout #2
connecting -> idle (timeout)
idle -> connecting
connecting -> connected
connected -> idle

优势对比

动态多态实现静态多态(std::variant)
使用 virtual,运行时决策使用 std::visit,编译期决策
需堆分配 std::unique_ptr所有状态栈上分配,零堆开销
类型不安全,易忘记 override编译器强制 on_event() 存在
新状态扩展需写派生类直接添加新 struct 和 case
dispatch 写法复杂std::visit 一行搞定
如果你还想要支持 带数据状态(比如连接次数、延迟等),只需在 struct 中添加成员变量即可。

讨论了用std::variant替代传统**动态多态(dynamic polymorphism)**的方式,特别是在“事件(event)携带数据”时该怎么设计。

我帮你梳理下核心点和背景,方便你理解:

背景

  • 传统动态多态(用基类指针或引用+虚函数)是经典的面向对象设计方法。
  • 你有一个基类 event,然后派生出具体事件,比如 event_connect,带有数据(比如 address_)。
  • 状态 state_idle 等通过虚函数 on_event(const event&) 处理不同事件。为了区分事件类型,通常会用 dynamic_cast 来尝试转换为具体事件类型。
std::unique_ptr<state> state_idle::on_event(const event& e)
{if (auto ptr = dynamic_cast<const event_connect*>(&e)) {// 处理 event_connect} else {// 处理其他事件}
}

这个方案的问题

  • dynamic_cast 是运行时开销较大的类型判断。
  • 多个事件派生类和状态类,on_event 函数变得臃肿、难维护。
  • 使用虚函数本质上依赖运行时类型信息(RTTI),违背现代C++中更倾向于静态类型安全零开销抽象的设计理念。

CppCon 2018 演讲提出的替代方案

  • std::variant 代替继承多态,将所有事件和状态都声明成不同类型的variant成员。
  • 利用 std::visit 静态分发事件,避免 dynamic_cast
  • 事件携带数据也通过 std::variant 里的具体事件类型存储,访问时类型安全且零开销。

“双重分发(Double Dispatch)”和“访问者模式(Visitor Pattern)”

  • 传统 OOP 的多态就是单重分发:函数调用根据单个对象的动态类型选择实现。
  • 但是状态机中on_event(state, event)函数需要根据两个对象的运行时类型(当前状态 + 事件类型)来分发,这叫做多重分发
  • 传统做法用双重分发(Visitor Pattern)复杂且笨重。

总结

  • 动态多态+dynamic_cast实现事件处理不优,会有性能和可维护性问题。
  • std::variant结合std::visit,以静态多态和模式匹配替代传统运行时多态,既安全又高效。
  • 这种方式,事件携带数据时也可以灵活处理,不用靠基类接口和强制转换。

双重分发(Double Dispatch)是面向对象编程中的一种技术,用于根据两个对象的运行时类型选择要调用的函数。

单重分发 vs 双重分发

单重分发(Single Dispatch):

这是 C++ 的默认行为:函数调用基于接收者(this指针)的运行时类型

struct Shape {virtual void draw() const = 0;
};
struct Circle : Shape {void draw() const override { std::cout << "Circle\n"; }
};
Shape* s = new Circle;
s->draw();  // 单重分发:只根据 *s 的类型(Circle)选择函数
但这对两个对象的协作不够:

比如你有两个对象:ShapeRenderer,你想根据两者的具体类型组合决定调用哪个函数,那就需要 双重分发

双重分发示例:撞击处理(Visitor 模式)

你希望这样写代码:

Shape* a = new Circle;
Shape* b = new Square;
a->collide_with(b); // 要根据 a 和 b 的具体类型组合决定行为
问题:

C++ 只支持单重分发,因此 a->collide_with(b) 只会根据 a 的类型决定调用哪个 collide_with 函数,无法根据 b 的类型再进一步分发

解决方法:双重分发(典型通过 Visitor 模式实现)

让第一个对象反向调用第二个对象的另一个虚函数:

struct Shape {virtual void collide_with(Shape* other) = 0;virtual void collide_with_circle(class Circle* c) = 0;virtual void collide_with_square(class Square* s) = 0;
};
struct Circle : Shape {void collide_with(Shape* other) override {other->collide_with_circle(this);  // 再调用另一个虚函数,第二次分发}void collide_with_circle(Circle*) override { std::cout << "Circle vs Circle\n"; }void collide_with_square(Square*) override { std::cout << "Circle vs Square\n"; }
};
struct Square : Shape {void collide_with(Shape* other) override {other->collide_with_square(this);}void collide_with_circle(Circle*) override { std::cout << "Square vs Circle\n"; }void collide_with_square(Square*) override { std::cout << "Square vs Square\n"; }
};

现在你可以:

Shape* a = new Circle;
Shape* b = new Square;
a->collide_with(b);  // 正确分发到 Circle vs Square

为什么叫“双重”?

因为:

  1. 第一次分发是 a->collide_with(b):根据 a 的类型调用合适的 collide_with
  2. 第二次分发是 b->collide_with_circle(a):根据 b 的类型调用合适的重载函数
    这就是 双重分发

std::variant 怎么做双重分发?

std::variant<Circle, Square> a, b;
std::visit([](auto&& obj1, auto&& obj2) {handle_collision(obj1, obj2);  // 编译期双重分发(静态类型组合)
}, a, b);

相比虚函数方式:

  • 不用写复杂的 Visitor 接口
  • 编译期检查所有组合是否实现
  • 零开销

总结一句话:

双重分发是函数调用同时依赖两个对象的运行时类型,传统通过虚函数+Visitor实现,现代 C++ 更推荐用 std::variant + std::visit 静态实现,类型安全、可扩展、零开销。

下面是一个使用 std::variantstd::visit 实现的“双重分发”完整示例,模拟了两个几何图形对象之间的“碰撞”(collision)逻辑。

使用 std::variant 实现双重分发(推荐方式)

#include <iostream>
#include <variant>
// 1. 定义图形类型
struct Circle {};
struct Square {};
struct Triangle {};
// 2. 定义所有可能的图形类型组合的碰撞行为
void collide(const Circle&, const Circle&) {std::cout << "Circle collides with Circle\n";
}
void collide(const Circle&, const Square&) {std::cout << "Circle collides with Square\n";
}
void collide(const Square&, const Circle&) {std::cout << "Square collides with Circle\n";
}
void collide(const Square&, const Square&) {std::cout << "Square collides with Square\n";
}
void collide(const Circle&, const Triangle&) {std::cout << "Circle collides with Triangle\n";
}
void collide(const Triangle&, const Square&) {std::cout << "Triangle collides with Square\n";
}
// 3. 类型别名
using Shape = std::variant<Circle, Square, Triangle>;
// 4. 双重分发
void collide(const Shape& s1, const Shape& s2) {std::visit([](auto&& a, auto&& b) {collide(a, b);  // 静态分发}, s1, s2);
}
// 5. 测试
int main() {Shape a = Circle{};Shape b = Square{};Shape c = Triangle{};collide(a, b);  // Circle collides with Squarecollide(b, a);  // Square collides with Circlecollide(a, a);  // Circle collides with Circlecollide(a, c);  // Circle collides with Trianglecollide(c, b);  // Triangle collides with Square
}

输出示例

Circle collides with Square
Square collides with Circle
Circle collides with Circle
Circle collides with Triangle
Triangle collides with Square

优点

  • 零运行时开销(编译期确定函数)
  • 类型安全(漏写组合时会编译错误)
  • 可扩展(添加新类型时只需加新函数)

双重分发(Double Dispatch)」和 Visitor 模式实现的状态机完整代码,并配有详细注释和结构化分析。

双重分发(Visitor Pattern)状态机完整代码与注释

#include <iostream>
#include <memory>
#include <string>
#include <string_view>
// -----------------------------
// 非拷贝基类,用于防止拷贝
// -----------------------------
struct noncopyable {noncopyable() = default;noncopyable(const noncopyable&) = delete;noncopyable& operator=(const noncopyable&) = delete;
};
// 前向声明
class state;
// -----------------------------
// 事件基类,模板参数是状态类型
// 每个具体事件都会继承它
// dispatch:第二次分发(到 state)
// -----------------------------
template<typename State>
struct event : private noncopyable {virtual ~event() = default;virtual std::unique_ptr<State> dispatch(State& s) const = 0;
};
// -----------------------------
// FSM 模板,持有当前状态,派发事件
// -----------------------------
template<typename State, typename Event>
class fsm {std::unique_ptr<State> state_;
public:explicit fsm(std::unique_ptr<State> state): state_(std::move(state)) {}void dispatch(const Event& e) {auto new_state = e.dispatch(*state_);if (new_state)state_ = std::move(new_state);}
};
// -----------------------------
// 所有事件类型(携带或不携带数据)
// -----------------------------
struct event_connect final : public event<state> {std::string_view address_;explicit event_connect(std::string_view addr) : address_(addr) {}std::string_view address() const { return address_; }std::unique_ptr<state> dispatch(state& s) const override;
};
struct event_connected final : public event<state> {std::unique_ptr<state> dispatch(state& s) const override;
};
struct event_disconnect final : public event<state> {std::unique_ptr<state> dispatch(state& s) const override;
};
struct event_timeout final : public event<state> {std::unique_ptr<state> dispatch(state& s) const override;
};
// -----------------------------
// 状态基类,声明对所有事件的响应接口
// 每个派生状态类可重写它需要响应的事件
// -----------------------------
class state : noncopyable {
public:virtual ~state() = default;// 默认处理所有事件为“忽略”virtual std::unique_ptr<state> on_event(const event_connect&) { return nullptr; }virtual std::unique_ptr<state> on_event(const event_connected&) { return nullptr; }virtual std::unique_ptr<state> on_event(const event_disconnect&) { return nullptr; }virtual std::unique_ptr<state> on_event(const event_timeout&) { return nullptr; }
};
// -----------------------------
// 空闲状态
// -----------------------------
class state_idle final : public state {
public:using state::on_event; // 继承其余 on_event 默认实现std::unique_ptr<state> on_event(const event_connect& e) override;
};
// -----------------------------
// 连接中状态
// -----------------------------
class state_connecting final : public state {std::string address_;int timeout_count_ = 0;
public:explicit state_connecting(std::string addr) : address_(std::move(addr)) {}std::unique_ptr<state> on_event(const event_connected&) override;std::unique_ptr<state> on_event(const event_timeout&) override;
};
// -----------------------------
// 已连接状态
// -----------------------------
class state_connected final : public state {
public:std::unique_ptr<state> on_event(const event_disconnect&) override;
};
// -----------------------------
// 各个事件的 dispatch 实现
// 调用状态类中的 on_event,形成第二次分发
// -----------------------------
std::unique_ptr<state> event_connect::dispatch(state& s) const {return s.on_event(*this);
}
std::unique_ptr<state> event_connected::dispatch(state& s) const {return s.on_event(*this);
}
std::unique_ptr<state> event_disconnect::dispatch(state& s) const {return s.on_event(*this);
}
std::unique_ptr<state> event_timeout::dispatch(state& s) const {return s.on_event(*this);
}
// -----------------------------
// 状态行为实现
// -----------------------------
std::unique_ptr<state> state_idle::on_event(const event_connect& e) {std::cout << "Idle -> Connecting to " << e.address() << "\n";return std::make_unique<state_connecting>(std::string(e.address()));
}
std::unique_ptr<state> state_connecting::on_event(const event_connected&) {std::cout << "Connecting -> Connected\n";return std::make_unique<state_connected>();
}
std::unique_ptr<state> state_connecting::on_event(const event_timeout&) {++timeout_count_;if (timeout_count_ >= 3) {std::cout << "Connecting -> Idle (timeout)\n";return std::make_unique<state_idle>();}std::cout << "Connecting: timeout #" << timeout_count_ << "\n";return nullptr;
}
std::unique_ptr<state> state_connected::on_event(const event_disconnect&) {std::cout << "Connected -> Idle\n";return std::make_unique<state_idle>();
}
// -----------------------------
// 主程序:测试 FSM 逻辑
// -----------------------------
int main() {fsm<state, event<state>> machine{std::make_unique<state_idle>()};event_connect connect{"example.com"};event_timeout timeout;event_connected connected;event_disconnect disconnect;machine.dispatch(connect);machine.dispatch(timeout);machine.dispatch(timeout);machine.dispatch(timeout);  // should go back to idlemachine.dispatch(connect);machine.dispatch(connected);machine.dispatch(disconnect);
}

核心概念理解

概念说明
单重分发根据一个对象类型决定调用哪个函数(如虚函数)
双重分发根据两个对象类型(状态和事件)决定调用哪个函数
Visitor 模式将操作和数据结构解耦,事件驱动调用具体状态逻辑
dispatch()在事件中调用状态的 on_event():动态分发
on_event()在状态中接收事件:事件类型重载形成第二层分发
状态切换返回新的 std::unique_ptr<state>,在 FSM 中替换当前状态

优点

  • 支持 携带数据的事件(如 connect 事件传地址)
  • 使用传统面向对象方式,便于理解
  • 模块化好,易于扩展新状态/新事件类型

提供的代码展示了 双重分发(Double Dispatch)+ 面向对象 FSM(状态机) 的完整实现。下面我将为你系统地分析和解释每个部分,让你能深刻理解它的结构、原理、运行机制,以及它为何被称为 “双重分发”。

1. 什么是双重分发?

单重分发:

C++ 虚函数的机制是根据对象的动态类型选择函数,例如:

Animal* a = new Dog;
a->speak();  // 调用的是 Dog::speak(),这是一次“单重分发”

双重分发(Double Dispatch):

需要根据两个对象的动态类型来决定调用哪个函数。
在本例中:

  • 第一次分发:事件调用状态的 dispatch()
  • 第二次分发:状态调用具体的 on_event() 来处理事件。
    所以我们有两个“动态绑定”的地方 → 双重分发

2. 架构组成和类关系图

                     +----------------+|   event<State> |<----------------------------------++----------------+                                   |^                                            |+----------------------+----------------------------+               ||                      |                            |               |
+----------------+  +---------------------+   +----------------------+
| event_connect  |  | event_timeout       |   | event_connected      | ...
+----------------+  +---------------------+   +----------------------+| dispatch(State&) |↓+-------------------+|      state        |+-------------------+^  ^  ^|  |  |+--------+  |  +-------------++----------------+   |                +------------------+| state_idle     |   |                | state_connected  |+----------------+   |                +------------------+|+--------------------+|  state_connecting   |+--------------------+
fsm<state, event<state>>

3. 核心组件说明

event<State>(事件基类)

template<typename State>
struct event {virtual std::unique_ptr<State> dispatch(State& s) const = 0;
};
  • 所有事件都继承自它。
  • 定义了虚函数 dispatch(),让事件自己去“触发”状态处理。

state(状态基类)

class state {virtual std::unique_ptr<state> on_event(const event_connect&) { return nullptr; }...
};
  • 定义了对所有事件类型的默认处理接口。
  • 子类重写这些函数,实现具体逻辑。
  • 返回新的状态(std::unique_ptr<state>),用以更新 FSM。

fsm<state, event<state>>

template<typename State, typename Event>
class fsm {std::unique_ptr<State> state_;void dispatch(const Event& e) {auto new_state = e.dispatch(*state_);if (new_state)state_ = std::move(new_state);}
};
  • 持有当前状态指针
  • dispatch() 把事件交给状态处理,通过双重分发来更新状态。

4. 状态转换逻辑详解

state_connecting 为例:

std::unique_ptr<state> state_connecting::on_event(const event_timeout&) {++timeout_count_;if (timeout_count_ >= 3) {std::cout << "Connecting -> Idle (timeout)\n";return std::make_unique<state_idle>();}std::cout << "Connecting: timeout #" << timeout_count_ << "\n";return nullptr;
}
  • 如果 timeout 连续发生 3 次 → 状态退回 idle。
  • 返回 nullptr 表示状态保持不变。

5. 测试代码逻辑

int main() {fsm<state, event<state>> machine{std::make_unique<state_idle>()};// 构造事件event_connect connect{"example.com"};event_timeout timeout;event_connected connected;event_disconnect disconnect;// 事件流machine.dispatch(connect);    // idle -> connectingmachine.dispatch(timeout);    // connecting: timeout #1machine.dispatch(timeout);    // connecting: timeout #2machine.dispatch(timeout);    // connecting -> idlemachine.dispatch(connect);    // idle -> connectingmachine.dispatch(connected);  // connecting -> connectedmachine.dispatch(disconnect); // connected -> idle
}

终端输出:

Idle -> Connecting to example.com
Connecting: timeout #1
Connecting: timeout #2
Connecting -> Idle (timeout)
Idle -> Connecting to example.com
Connecting -> Connected
Connected -> Idle

优势总结

特性说明
双重分发同时考虑“事件类型”与“状态类型”来决定行为
面向对象状态和事件都是对象,封装良好
易扩展新增状态或事件,只需新增类并重写相应方法
支持携带数据的事件event_connect 可以传递 address

std::variant 的对比(可选)

特性variant 实现OOP 双重分发实现
性能编译期类型选择,无虚表运行时虚函数开销
可扩展性(新增事件)不易扩展,需要修改 std::visit易扩展,新建 event 派生类即可
可扩展性(新增状态)同样不易扩展易扩展,新建 state 派生类即可
数据传递需要 std::variant 携带数据每个事件类型类可以自带数据成员
event
state* dispatch(state&)
fsm
+dispatch(const event&)
state
+on_event(const event connect&)
+on_event(const event connected&)
+on_event(const event disconnect&)
+on_event(const event timeout&)
state_idle
state_connecting
+std::string address
state_connected
event_connect
+std::string_view address
event_connected
event_disconnect
event_timeout

mermaid code

classDiagramclass event {state* dispatch(state&) = 0}class fsm {+dispatch(const event&)}class state {+on_event(const event connect&)+on_event(const event connected&)+on_event(const event disconnect&)+on_event(const event timeout&)}class state_idleclass state_connecting {+std::string address}class state_connectedclass event_connect {+std::string_view address}class event_connectedclass event_disconnectclass event_timeoutstate <|-- state_idlestate <|-- state_connectingstate <|-- state_connectedfsm --> stateevent <|.. fsmevent <|-- event_connectevent <|-- event_disconnectevent <|-- event_timeoutevent <|-- event_connected

下面是你提供的代码中所展示的 CRTP(Curiously Recurring Template Pattern)+ 双重分发 FSM(有限状态机)完整示例代码、注释、分析和理解总结

CRTP 状态机完整代码(加注释)

#include <iostream>
#include <memory>
#include <string>
#include <string_view>
// -----------------------------
// 工具类:不可拷贝 base
// -----------------------------
struct noncopyable {noncopyable() = default;noncopyable(const noncopyable&) = delete;noncopyable& operator=(const noncopyable&) = delete;
};
// 前向声明
class state;
// -----------------------------
// 1. 抽象事件接口:带虚函数 dispatch
// -----------------------------
template<typename State>
struct event : private noncopyable {virtual ~event() = default;virtual std::unique_ptr<State> dispatch(State& s) const = 0;
};
// -----------------------------
// 2. CRTP 基类:模板参数是派生类自身
// -----------------------------
template<typename Child>
struct event_crtp : public event<state> {std::unique_ptr<state> dispatch(state& s) const override {// 第二次分发 -> 状态调用具体事件处理return s.on_event(*static_cast<const Child*>(this));}
};
// -----------------------------
// 3. 所有事件类型(通过 CRTP 实现 dispatch)
// -----------------------------
struct event_connect final : public event_crtp<event_connect> {explicit event_connect(std::string_view address) : address_(address) {}std::string_view address() const { return address_; }
private:std::string_view address_;
};
struct event_connected final : public event_crtp<event_connected> {};
struct event_disconnect final : public event_crtp<event_disconnect> {};
struct event_timeout final : public event_crtp<event_timeout> {};
// -----------------------------
// 4. 状态基类:定义所有 on_event 重载接口
// -----------------------------
class state : noncopyable {
public:virtual ~state() = default;virtual std::unique_ptr<state> on_event(const event_connect&) { return nullptr; }virtual std::unique_ptr<state> on_event(const event_connected&) { return nullptr; }virtual std::unique_ptr<state> on_event(const event_disconnect&) { return nullptr; }virtual std::unique_ptr<state> on_event(const event_timeout&) { return nullptr; }
};
// -----------------------------
// 5. 具体状态类定义和实现
// -----------------------------
// 空闲状态
class state_idle final : public state {
public:using state::on_event;  // 保留默认处理std::unique_ptr<state> on_event(const event_connect& e) override {std::cout << "Idle -> Connecting to " << e.address() << "\n";return std::make_unique<class state_connecting>(std::string{e.address()});}
};
// 正在连接状态
class state_connecting final : public state {std::string address_;int n = 0;static constexpr int n_max = 3;
public:explicit state_connecting(std::string addr) : address_(std::move(addr)) {}std::unique_ptr<state> on_event(const event_connected&) override {std::cout << "Connecting -> Connected\n";return std::make_unique<class state_connected>();}std::unique_ptr<state> on_event(const event_timeout&) override {++n;if (n >= n_max) {std::cout << "Connecting -> Idle (timeout)\n";return std::make_unique<state_idle>();}std::cout << "Connecting: timeout #" << n << "\n";return nullptr;}
};
// 已连接状态
class state_connected final : public state {
public:std::unique_ptr<state> on_event(const event_disconnect&) override {std::cout << "Connected -> Idle\n";return std::make_unique<state_idle>();}
};
// -----------------------------
// 6. FSM 管理器模板
// -----------------------------
template<typename State, typename Event>
class fsm {std::unique_ptr<State> state_;
public:explicit fsm(std::unique_ptr<State> s) : state_(std::move(s)) {}void dispatch(const Event& e) {auto new_state = e.dispatch(*state_);if (new_state) {state_ = std::move(new_state);}}
};
// -----------------------------
// 7. 主程序:运行测试
// -----------------------------
int main() {fsm<state, event<state>> connection_fsm{std::make_unique<state_idle>()};connection_fsm.dispatch(event_connect{"example.com"});connection_fsm.dispatch(event_timeout{});connection_fsm.dispatch(event_timeout{});connection_fsm.dispatch(event_timeout{});  // 连接失败回到 idleconnection_fsm.dispatch(event_connect{"another.com"});connection_fsm.dispatch(event_connected{});connection_fsm.dispatch(event_disconnect{});
}

CRTP(Curiously Recurring Template Pattern)简析

原理

CRTP 是一种 模板与继承结合的技巧,派生类将自己作为模板参数传给基类:

template<typename Derived>
class Base {void foo() { static_cast<Derived*>(this)->bar(); }
};
class Derived : public Base<Derived> {void bar();
};

在这个 FSM 中的用法:

  • event_crtp<Child> 是模板基类,提供统一的 dispatch() 实现。
  • 子类如 event_connect 继承 event_crtp<event_connect>,自动拥有 dispatch。
  • 避免了每个事件类都手写 dispatch() 的重复代码。

双重分发完整链条:

  1. fsm::dispatch() 调用事件的 dispatch(state&) → 第一次分发(事件分发给状态);
  2. event_crtp::dispatch() 调用状态的 on_event(*this) → 第二次分发(根据事件类型,进入状态类的特定重载);
  3. state_idle::on_event(const event_connect&) 等是根据事件类型决定的虚函数重载。
    这就是双重分发(double dispatch)的完整实现。

总结:优点

特性说明
清晰的状态转换每个状态类管理自己的转移逻辑
双重分发利用虚函数支持“基于状态 + 事件”的派发
CRTP 简化事件定义每个事件不再重复写 dispatch()
面向对象扩展性好增加新状态或事件都很简单
数据封装能力强event_connect 携带 address 数据

如果你想继续:

  • std::variant 实现替代这套双重分发;
  • 使用现代 std::visit 实现 FSM;
  • 用 Boost.SML 比较这套手写实现;
  • 把状态保存成 JSON 等形式可序列化;

状态机实现的内容,具体涵盖了:

  • 双重分发(Double Dispatch)
  • Fold Expression 测试多个状态转换
  • 对象模型的局限
  • 事件为指针形式传递
  • 对新操作和新事件的开放/封闭性分析
    下面我逐点帮你解释、分析并提供对应的完整测试代码。

1. TESTING TRANSITIONS with fold expression

template<typename Fsm, typename... Events>
void dispatch(Fsm& fsm, const Events&... events)
{(fsm.dispatch(*events), ...);  // C++17 fold expression
}

理解:

这段代码是用于测试状态机逻辑是否能按预期流转,通过传入多个事件(以 unique_ptr 形式),一次性全部派发。

2. 使用示例:

dispatch(fsm,std::make_unique<event_connect>("train-it.eu"),std::make_unique<event_timeout>(),std::make_unique<event_connected>(),std::make_unique<event_disconnect>());

理解:

  • 每个 event_* 都是 event<state> 的派生类,存储在 unique_ptr 中。
  • dispatch 函数解包这些指针并调用 fsm.dispatch(*e)
  • 通过这种方式可以方便模拟一连串状态转换行为

3. 为什么需要 std::unique_ptr<event>

  • 因为事件是通过虚函数进行分发的(需要多态);
  • 所以必须以 event<state> 的指针(或引用)来传递;
  • 这里选择 std::unique_ptr<event<state>> 是为了资源管理更安全。

4. THE SLOW PART

这段指的是使用这种双重虚函数(dispatch 调用 -> on_event)实现方式:

运行时有两个虚函数跳转,这就是“慢”的部分。

fsm.dispatch(*event);  // 1st virtual dispatch: event::dispatch()// 2nd virtual dispatch: state::on_event()

每次调用都涉及两个动态分发(vtable 调用):

  1. 事件 dispatch;
  2. 状态的 on_event 重载。
    所以对于性能敏感场景,可以考虑替代机制,如:
  • std::variant + std::visit(编译期分发)
  • Boost.SML(静态状态机)

5. Double Dynamic Dispatch(双重分发)小结

优点:

特性描述
面向对象易于扩展状态和事件
高解耦状态与事件是解耦的类
多级继承支持状态、事件可多层级继承

缺点:

问题描述
无法添加新操作新的行为(如调试、日志)必须改基类
扩展受限派生层次固定,不适合插件系统
性能偏低双重虚函数跳转,不能内联优化

总结理解

双重分发的流程是:

fsm.dispatch(event_ptr)→ event_ptr->dispatch(state)→ state->on_event(event)

fold expression 调用流程是:

dispatch(fsm, event1, event2, event3);→ fsm.dispatch(*event1)→ fsm.dispatch(*event2)...

最后:测试完整代码片段

template<typename Fsm, typename... Events>
void dispatch(Fsm& fsm, const Events&... events)
{(fsm.dispatch(*events), ...);
}
int main() {fsm<state, event<state>> fsm_machine{std::make_unique<state_idle>()};dispatch(fsm_machine,std::make_unique<event_connect>("train-it.eu"),std::make_unique<event_timeout>(),std::make_unique<event_connected>(),std::make_unique<event_disconnect>());
}

后面部分太难看不懂

http://www.lqws.cn/news/592237.html

相关文章:

  • Helix Toolkit 在 WPF 中加载带贴图素材的模型
  • 《全程软件测试》第1章
  • 坚石ET ARM加密狗复制模拟介绍
  • 23.安卓逆向2-r0capture搭配Wireshark方式抓包
  • Nuxt 3 中实现跨组件通信方式总结:使用 Pinia、Provide/Inject 或 Props
  • 设计模式-命令模式
  • 昆泰芯3D霍尔磁传感器芯片在汽车零部件中的应用
  • OpenCV-Python Tutorial : A Candy from Official Main Page(二)
  • 使用FFmpeg+SDL2读取本地摄像头并渲染
  • 07 Springboot+netty+mqtt服务端实现【重构】
  • php-mqtt/client 发布、订阅
  • 学习threejs,使用自定义GLSL 着色器,生成艺术作品
  • Redis-渐进式遍历
  • Android实现仿iOS风格滚动时间选择器
  • 【机器学习深度学习】理解欠拟合、拟合、过拟合
  • React安装使用教程
  • Linux->进程控制(精讲)
  • 文心一言开源版测评:能力、易用性与价值的全面解析
  • 通过http调用来访问neo4j时报错,curl -X POST 执行指令报错
  • 博途多重背景、参数实例
  • swing音频输入
  • 跨境证券交易系统合规升级白皮书:全链路微秒风控+开源替代,护航7月程序化交易新规落地
  • 7.可视化的docker界面——portainer
  • CloudBase AI ToolKit实战:从0到1开发一个智能医疗网站
  • LLM中的思维链,仅仅在提示词中加上思维链,让模型输出,和加上思维链限制模型输出答案,这两方式模型是不是进行了思考的
  • 鸿蒙Next开发中三方库使用指南之-nutpi-privacy_dialog集成示例
  • 用“做饭”理解数据分析流程(Excel三件套实战)​
  • 网站崩溃的幕后黑手:GPTBot爬虫的流量冲击
  • 论文阅读:Align and Prompt (ALPRO 2021.12)
  • 零开始git使用教程-传html文件