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

"Effective Replacement of Dynamic Polymorphism with std::variant" 是现代 C++ 中一个重要的设计思路:用静态类型机制(如 std::variant)来替代传统的虚函数(动态多态) ,从而实现 更安全、更高效、更易维护的多态行为

下面我来详细讲解这个设计理念、适用场景、优缺点、和对比。

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

cpp 复制代码
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 代替虚函数

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

特点

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

三、适用场景对比

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

四、关键点总结

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

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

cpp 复制代码
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)替代。

原始代码解释

cpp 复制代码
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 来代替:

cpp 复制代码
#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);  // 输出:x
    v = y{};
    call_foo(v);  // 输出:y
}

动态多态 vs std::variant 对比

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

总结:如何选择?

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

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

你原始代码:

cpp 复制代码
std::unique_ptr<base> b = std::make_unique<x>();
b->foo();

用现代方式替换为:

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

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

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

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

cpp 复制代码
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

cpp 复制代码
#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);  // 输出 x
    b = 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
cpp 复制代码
struct apple { void foo(); };
struct orange { void foo(); };
// apple 和 orange 没有继承关系,但 std::variant 可以接受

2. 更快、更短

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

3. 适用范围

适合

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

例子总结(对比)

动态多态方式:

cpp 复制代码
std::unique_ptr<base> b = std::make_unique<x>();
b->foo();

std::variant 方式:

cpp 复制代码
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(简化版)

cpp 复制代码
#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 Playing
    handle_event(current, "pause");  // Paused
    handle_event(current, "play");   // Resuming
    handle_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_idle
    state_idle --> state_connecting : connect
    state_connecting --> state_connected : connected
    state_connecting --> state_idle : timeout [n = n_max]
    state_connected --> state_connecting : disconnect
    state_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):

From To Trigger/Event (with condition)
[*] state_idle 初始状态
state_idle state_connecting connect 事件
state_connecting state_connected connected 事件(连接成功)
state_connecting state_idle timeoutn == n_max
state_connecting state_connecting timeoutn < n_max(重试)
state_connected state_connecting disconnect 事件(主动断开)

条件(Guards):

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

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

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

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

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

cpp 复制代码
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:

复制代码
classDiagram
    class event {
        +connect
        +connected
        +disconnect
        +timeout
    }
    class fsm {
        +dispatch(event)
    }
    class state {
        +on_event(event) : 0
    }
    class state_idle
    class state_connecting
    class state_connected
    fsm o--> state
    state o--> state_idle
    state o--> state_connecting
    state o--> state_connected

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

目标:

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

代码+注释+分析

cpp 复制代码
// 声明状态接口,基于事件类型进行参数化
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 → 状态不变。
cpp 复制代码
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 实现资源管理和所有权转移。

示例用途(假设):

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

cpp 复制代码
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;
    }
};

用法:

cpp 复制代码
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)进行转换。

整体结构

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

完整代码 + 注释 + 分析

cpp 复制代码
#include <iostream>
#include <memory>
enum class event {
    connect,
    connected,
    timeout,
    disconnect
};

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

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

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

cpp 复制代码
// 有限状态机类
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 持有并替换旧状态。
cpp 复制代码
using s = state<event>; // 简化写法

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

具体状态类

cpp 复制代码
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 的响应逻辑

cpp 复制代码
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 的响应逻辑

cpp 复制代码
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 的响应逻辑

cpp 复制代码
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 类

cpp 复制代码
class connection_fsm : public fsm<event> {
public:
    connection_fsm()
        : fsm<event>(std::make_unique<state_idle>()) {}
};

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

用法示例(添加主函数)

cpp 复制代码
int main() {
    connection_fsm conn;
    conn.dispatch(event::connect);    // idle -> connecting
    conn.dispatch(event::timeout);    // retry 1
    conn.dispatch(event::timeout);    // retry 2
    conn.dispatch(event::timeout);    // retry 3, back to idle
    conn.dispatch(event::connect);    // idle -> connecting
    conn.dispatch(event::connected);  // connecting -> connected
    conn.dispatch(event::disconnect); // connected -> idle
}

总结

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

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

测试状态转换(Testing Transitions)

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

解释:

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

cpp 复制代码
(fsm.dispatch(events), ...);

等价于:

cpp 复制代码
fsm.dispatch(event1);
fsm.dispatch(event2);
fsm.dispatch(event3);
// ...

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

示例用法:

cpp 复制代码
connection_fsm fsm;
dispatch(fsm, event::connect, event::timeout, event::connected, event::disconnect);

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

复制代码
Transition: idle -> connecting
Timeout count: 1
Transition: connecting -> connected
Transition: connected -> idle

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

Single Dynamic Dispatch 的优缺点分析

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

为何要换成 std::variant

因为:

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

总结理解

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

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

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

cpp 复制代码
#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 事件,状态转为 connecting
        std::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次超时后回到 idle
            if (++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 事件,状态转为 idle
        std::cout << "connected -> idle\n";
        return state_idle{};
    }
    return *this;  // 其他事件状态不变
}
// 状态机定义
struct connection_fsm {
    state current;          // 当前状态,存储为variant
    int 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{}};  // 初始状态为 idle
    dispatch(fsm,
             event::connect,    // idle -> connecting
             event::timeout,    // timeout #1
             event::timeout,    // timeout #2
             event::timeout,    // timeout #3 -> idle (超时重置)
             event::connect,    // idle -> connecting
             event::connected,  // connecting -> connected
             event::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 来尝试转换为具体事件类型。
cpp 复制代码
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指针)的运行时类型

cpp 复制代码
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 模式)

你希望这样写代码:

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

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

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

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

cpp 复制代码
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"; }
};

现在你可以:

cpp 复制代码
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 怎么做双重分发?

cpp 复制代码
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 实现双重分发(推荐方式)

cpp 复制代码
#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 Square
    collide(b, a);  // Square collides with Circle
    collide(a, a);  // Circle collides with Circle
    collide(a, c);  // Circle collides with Triangle
    collide(c, b);  // Triangle collides with Square
}

输出示例

txt 复制代码
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)状态机完整代码与注释

cpp 复制代码
#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 idle
    machine.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++ 虚函数的机制是根据对象的动态类型选择函数,例如:

cpp 复制代码
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>(事件基类)

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

state(状态基类)

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

fsm<state, event<state>>

cpp 复制代码
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 为例:

cpp 复制代码
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. 测试代码逻辑

cpp 复制代码
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 -> connecting
    machine.dispatch(timeout);    // connecting: timeout #1
    machine.dispatch(timeout);    // connecting: timeout #2
    machine.dispatch(timeout);    // connecting -> idle
    machine.dispatch(connect);    // idle -> connecting
    machine.dispatch(connected);  // connecting -> connected
    machine.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

复制代码
classDiagram
    class 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_idle
    class state_connecting {
        +std::string address
    }
    class state_connected
    class event_connect {
        +std::string_view address
    }
    class event_connected
    class event_disconnect
    class event_timeout
    state <|-- state_idle
    state <|-- state_connecting
    state <|-- state_connected
    fsm --> state
    event <|.. fsm
    event <|-- event_connect
    event <|-- event_disconnect
    event <|-- event_timeout
    event <|-- event_connected

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

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

cpp 复制代码
#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{});  // 连接失败回到 idle
    connection_fsm.dispatch(event_connect{"another.com"});
    connection_fsm.dispatch(event_connected{});
    connection_fsm.dispatch(event_disconnect{});
}

CRTP(Curiously Recurring Template Pattern)简析

原理

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

cpp 复制代码
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

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

理解:

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

2. 使用示例:

cpp 复制代码
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)实现方式:

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

cpp 复制代码
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 调用流程是:

cpp 复制代码
dispatch(fsm, event1, event2, event3);
 → fsm.dispatch(*event1)
 → fsm.dispatch(*event2)
 → ...

最后:测试完整代码片段

cpp 复制代码
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>());
}

后面部分太难看不懂

相关推荐
??tobenewyorker几秒前
力扣打卡第二十一天 中后遍历+中前遍历 构造二叉树
数据结构·c++·算法·leetcode
rzl0227 分钟前
java web5(黑马)
java·开发语言·前端
jingling55540 分钟前
面试版-前端开发核心知识
开发语言·前端·javascript·vue.js·面试·前端框架
我是小哪吒2.01 小时前
书籍推荐-《对抗机器学习:攻击面、防御机制与人工智能中的学习理论》
人工智能·深度学习·学习·机器学习·ai·语言模型·大模型
oioihoii1 小时前
C++11 forward_list 从基础到精通:原理、实践与性能优化
c++·性能优化·list
m0_687399841 小时前
写一个Ununtu C++ 程序,调用ffmpeg API, 来判断一个数字电影的视频文件mxf 是不是Jpeg2000?
开发语言·c++·ffmpeg
爱上语文1 小时前
Redis基础(5):Redis的Java客户端
java·开发语言·数据库·redis·后端
A~taoker1 小时前
taoker的项目维护(ng服务器)
java·开发语言
✎ ﹏梦醒͜ღ҉繁华落℘1 小时前
WPF学习(四)
学习·wpf
萧曵 丶1 小时前
Rust 中的返回类型
开发语言·后端·rust