C++实现状态机的四种方法

 更新时间:2026年06月02日 09:21:32   作者:筠筠喵呜喵  
本文主要介绍了状态机实现的四种方法,包括Switch、stdstd::variant&std::visit、查表法和状态模式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

1.Switch

  • 优点:直观、零额外抽象、性能最好
  • 缺点:case 越多越臃肿,逻辑分散。
#include <iostream>

enum class State { Idle, Running, Paused, Error };
enum class Event { Start, Pause, Resume, Stop, ErrorOccur, Reset };

struct SM {
    State s = State::Idle;
    void dispatch(Event ev) {
        switch (s) {
            case State::Idle:
                if (ev == Event::Start) { enter(State::Running); }
                else if (ev == Event::ErrorOccur) { enter(State::Error); }
                break;
            case State::Running:
                if (ev == Event::Pause) { enter(State::Paused); }
                else if (ev == Event::Stop) { enter(State::Idle); }
                else if (ev == Event::ErrorOccur) { enter(State::Error); }
                break;
            case State::Paused:
                if (ev == Event::Resume) { enter(State::Running); }
                else if (ev == Event::Stop) { enter(State::Idle); }
                else if (ev == Event::ErrorOccur) { enter(State::Error); }
                break;
            case State::Error:
                if (ev == Event::Reset) { enter(State::Idle); }
                break;
        }
    }
    void enter(State ns) {
        onExit(s);
        s = ns;
        onEnter(s);
    }
    void onEnter(State st) {
        std::cout << "enter " << toString(st) << "\n";
    }
    void onExit(State st) {
        std::cout << "exit " << toString(st) << "\n";
    }
    static const char* toString(State st) {
        switch (st) {
            case State::Idle: return "Idle";
            case State::Running: return "Running";
            case State::Paused: return "Paused";
            case State::Error: return "Error";
        }
        return "Unknown";
    }
};

int main() {
    SM sm;
    sm.dispatch(Event::Start);
    sm.dispatch(Event::Pause);
    sm.dispatch(Event::Resume);
    sm.dispatch(Event::ErrorOccur);
    sm.dispatch(Event::Reset);
    return 0;
}

2. std::variant + std::visit

  • 优点:每个状态行为封装成类型,类型安全且可内联(无虚调用)。
  • 缺点:增加类型数量,添加新状态需改 variant 列表和工厂。
#include <iostream>
#include <variant>
#include <memory>

struct Context;

// 各状态类型封装行为
struct Idle {
    static void onEnter(Context&);
    static void onExit(Context&);
    static void handle(Context&, int ev);
    static constexpr const char* name = "Idle";
};
struct Running {
    static void onEnter(Context&);
    static void onExit(Context&);
    static void handle(Context&, int ev);
    static constexpr const char* name = "Running";
};
struct Paused {
    static void onEnter(Context&);
    static void onExit(Context&);
    static void handle(Context&, int ev);
    static constexpr const char* name = "Paused";
};
struct ErrorSt {
    static void onEnter(Context&);
    static void onExit(Context&);
    static void handle(Context&, int ev);
    static constexpr const char* name = "Error";
};

using StateVar = std::variant<Idle, Running, Paused, ErrorSt>;

struct Context {
    StateVar state{Idle{}};
    void dispatch(int ev) {
        std::visit([&](auto &st){
            using T = std::decay_t<decltype(st)>;
            T::handle(*this, ev);
        }, state);
    }
    void setState(StateVar ns) {
        std::visit([&](auto &st){ using T = std::decay_t<decltype(st)>; T::onExit(*this); }, state);
        state = std::move(ns);
        std::visit([&](auto &st){ using T = std::decay_t<decltype(st)>; T::onEnter(*this); }, state);
    }
    void log(const char* msg) { std::cout << msg << "\n"; }
};

void Idle::onEnter(Context& c){ c.log("enter Idle"); }
void Idle::onExit(Context& c){ c.log("exit Idle"); }
void Idle::handle(Context& c, int ev){
    if(ev==1) c.setState(Running{});
    else if(ev==99) c.setState(ErrorSt{});
    else c.log("Idle ignore");
}

void Running::onEnter(Context& c){ c.log("enter Running"); }
void Running::onExit(Context& c){ c.log("exit Running"); }
void Running::handle(Context& c, int ev){
    if(ev==2) c.setState(Paused{});
    else if(ev==0) c.setState(Idle{});
    else if(ev==99) c.setState(ErrorSt{});
    else c.log("Running ignore");
}

void Paused::onEnter(Context& c){ c.log("enter Paused"); }
void Paused::onExit(Context& c){ c.log("exit Paused"); }
void Paused::handle(Context& c, int ev){
    if(ev==3) c.setState(Running{});
    else if(ev==0) c.setState(Idle{});
    else if(ev==99) c.setState(ErrorSt{});
    else c.log("Paused ignore");
}

void ErrorSt::onEnter(Context& c){ c.log("enter Error"); }
void ErrorSt::onExit(Context& c){ c.log("exit Error"); }
void ErrorSt::handle(Context& c, int ev){
    if(ev==4) c.setState(Idle{});
    else c.log("Error ignore");
}

int main(){
    Context ctx;
    ctx.dispatch(1);   // Start -> Running
    ctx.dispatch(2);   // Pause -> Paused
    ctx.dispatch(3);   // Resume -> Running
    ctx.dispatch(99);  // Error -> Error
    ctx.dispatch(4);   // Reset -> Idle
    return 0;
}

3.查表法

  • 优点:配置化、数据驱动、容易以表格/文件维护和测试,性能极高。
  • 缺点:运行时查表开销、复杂行为(guards/enter/exit)仍需回调。动作(action)与状态分离,扩展性较差
#include <iostream>
#include <map>
#include <functional>
#include <tuple>

enum class State { Idle, Running, Paused, Error };
enum class Event { Start, Pause, Resume, Stop, ErrorOccur, Reset };

// 表项:(State,Event) -> (new State, action)
using Key = std::pair<State, Event>;
struct Action { State next; std::function<void()> act; };

struct SM {
    std::map<Key, Action> table;
    State s = State::Idle;
    SM() {
        table[{State::Idle, Event::Start}] = {State::Running, [this](){ log("Idle->Running"); }};
        table[{State::Idle, Event::ErrorOccur}] = {State::Error, [this](){ log("Idle->Error"); }};
        table[{State::Running, Event::Pause}] = {State::Paused, [this](){ log("Running->Paused"); }};
        table[{State::Running, Event::Stop}] = {State::Idle, [this](){ log("Running->Idle"); }};
        table[{State::Running, Event::ErrorOccur}] = {State::Error, [this](){ log("Running->Error"); }};
        table[{State::Paused, Event::Resume}] = {State::Running, [this](){ log("Paused->Running"); }};
        table[{State::Paused, Event::Stop}] = {State::Idle, [this](){ log("Paused->Idle"); }};
        table[{State::Paused, Event::ErrorOccur}] = {State::Error, [this](){ log("Paused->Error"); }};
        table[{State::Error, Event::Reset}] = {State::Idle, [this](){ log("Error->Idle"); }};
    }

    void dispatch(Event ev) {
        auto it = table.find({s, ev});
        if (it != table.end()) {
            onExit(s);
            it->second.act();
            s = it->second.next;
            onEnter(s);
        } else {
            log("no transition");
        }
    }

    void onEnter(State st){ std::cout << "enter " << name(st) << "\n"; }
    void onExit(State st){ std::cout << "exit " << name(st) << "\n"; }
    void log(const char* m){ std::cout << m << "\n"; }
    static const char* name(State st){
        switch(st){ case State::Idle: return "Idle"; case State::Running: return "Running"; case State::Paused: return "Paused"; case State::Error: return "Error"; }
        return "Unknown";
    }
};

int main(){
    SM sm;
    sm.dispatch(Event::Start);
    sm.dispatch(Event::Pause);
    sm.dispatch(Event::Resume);
    sm.dispatch(Event::ErrorOccur);
    sm.dispatch(Event::Reset);
    return 0;
}

4.状态模式(虚函数实现)

  • 优点:面向对象、可在状态间封装复杂 enter/exit 行为,运行时灵活。
  • 缺点:类数量与抽象开销(虚函数/堆分配),需谨慎优化(对象复用或单例状态可减开销)。
#include <iostream>
#include <memory>
#include <string>

enum class Event { Start, Pause, Resume, Stop, ErrorOccur, Reset };

class Context;
struct State {
    virtual ~State() = default;
    virtual void onEnter(Context&) {}
    virtual void onExit(Context&) {}
    virtual void handle(Context&, Event) = 0;
    virtual std::string name() const = 0;
};

class Context {
public:
    void setState(std::unique_ptr<State> s) {
        if (state) state->onExit(*this);
        state = std::move(s);
        if (state) state->onEnter(*this);
    }
    void handle(Event ev) {
        if (state) state->handle(*this, ev);
    }
    void log(const std::string &m) { std::cout << m << "\n"; }
private:
    std::unique_ptr<State> state;
};

// Concrete states
struct Idle : State {
    void onEnter(Context& c) override { c.log("enter Idle"); }
    void onExit(Context& c) override { c.log("exit Idle"); }
    void handle(Context& c, Event ev) override;
    std::string name() const override { return "Idle"; }
};
struct Running : State {
    void onEnter(Context& c) override { c.log("enter Running"); }
    void onExit(Context& c) override { c.log("exit Running"); }
    void handle(Context& c, Event ev) override;
    std::string name() const override { return "Running"; }
};
struct Paused : State {
    void onEnter(Context& c) override { c.log("enter Paused"); }
    void onExit(Context& c) override { c.log("exit Paused"); }
    void handle(Context& c, Event ev) override;
    std::string name() const override { return "Paused"; }
};
struct ErrorState : State {
    void onEnter(Context& c) override { c.log("enter Error"); }
    void onExit(Context& c) override { c.log("exit Error"); }
    void handle(Context& c, Event ev) override;
    std::string name() const override { return "Error"; }
};

void Idle::handle(Context& c, Event ev){
    if(ev==Event::Start) c.setState(std::make_unique<Running>());
    else if(ev==Event::ErrorOccur) c.setState(std::make_unique<ErrorState>());
    else c.log("Idle ignore");
}
void Running::handle(Context& c, Event ev){
    if(ev==Event::Pause) c.setState(std::make_unique<Paused>());
    else if(ev==Event::Stop) c.setState(std::make_unique<Idle>());
    else if(ev==Event::ErrorOccur) c.setState(std::make_unique<ErrorState>());
    else c.log("Running ignore");
}
void Paused::handle(Context& c, Event ev){
    if(ev==Event::Resume) c.setState(std::make_unique<Running>());
    else if(ev==Event::Stop) c.setState(std::make_unique<Idle>());
    else if(ev==Event::ErrorOccur) c.setState(std::make_unique<ErrorState>());
    else c.log("Paused ignore");
}
void ErrorState::handle(Context& c, Event ev){
    if(ev==Event::Reset) c.setState(std::make_unique<Idle>());
    else c.log("Error ignore");
}

int main(){
    Context ctx;
    ctx.setState(std::make_unique<Idle>());
    ctx.handle(Event::Start);
    ctx.handle(Event::Pause);
    ctx.handle(Event::Resume);
    ctx.handle(Event::ErrorOccur);
    ctx.handle(Event::Reset);
    return 0;
}

5. 方案对比总结

方案优点缺点适用场景
switch1. 最简单直观
2. 零额外抽象,性能最好(编译期确定)
3. 代码集中,便于理解
1. 状态/事件增多时,switch-case 臃肿
2. 逻辑分散,可维护性差
3. 难以扩展(添加新状态需修改多处)
状态机规模小(<10 个状态),性能要求极高,且不预期频繁变更的场景
std::variant + std::visit1. 类型安全,每个状态行为封装为独立类型
2. 无虚函数调用,可内联优化
3. 编译期检查,避免遗漏状态处理
1. 类型数量增加,代码量较大
2. 添加新状态需修改 variant 列表和工厂
3. 对 C++17 及以上版本有要求
状态集固定,希望将行为与类型绑定,且追求高性能、类型安全的场景
查表法1. 高度配置化、数据驱动
2. 易于维护和测试(表格/文件可外部化)
3. 性能极高(O(1) 查表)
4. 状态转换逻辑集中
1. 运行时查表有轻微开销
2. 复杂行为(guard 条件、enter/exit)仍需回调函数
3. 动作与状态分离,扩展性较差
状态转换规则稳定、希望外部配置、便于测试和动态调整的场景
状态模式(虚函数)1. 面向对象,符合开闭原则
2. 易于管理复杂的 enter/exit 行为
3. 运行时灵活,状态可动态替换
4. 结构清晰,职责分离
1. 虚函数调用开销(运行时多态)
2. 类数量多,可能涉及堆分配
3. 若状态对象非单例,会有对象创建开销
状态行为复杂、需要封装大量状态专属逻辑、且预期会频繁扩展的场景

到此这篇关于C++实现状态机的四种方法的文章就介绍到这了,更多相关C++ 状态机内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C语言实现数字游戏

    C语言实现数字游戏

    这篇文章主要为大家详细介绍了C语言实现数字游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-01-01
  • Visual Studio Code (vscode) 配置C、C++环境/编写运行C、C++的教程详解(Windows)【真正的小白版】

    Visual Studio Code (vscode) 配置C、C++环境/编写运行C、C++的教程详解(Windows

    这篇文章主要介绍了Visual Studio Code (vscode) 配置C、C++环境/编写运行C、C++的教程详解(Windows)【真正的小白版】,图文详解介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-03-03
  • C++ const的各种用法详解

    C++ const的各种用法详解

    const名叫常量限定符,用来限定特定变量,以通知编译器该变量是不可修改的。习惯性的使用const,可以避免在函数中对某些不应修改的变量造成可能的改动。本文主要谈谈const的用法,感兴趣的同学可以参考阅读
    2023-04-04
  • 一文详解Qt中线程的实际应用

    一文详解Qt中线程的实际应用

    为了让程序尽快响应用户操作,在开发应用程序时经常会使用到线程。这篇文章就来和大家介绍一下Qt中线程的实际应用,感兴趣的小伙伴可以了解一下
    2023-03-03
  • C++ 类访问控制的条件总结

    C++ 类访问控制的条件总结

    这篇文章主要介绍了C++ 类访问控制的条件总结的相关资料,需要的朋友可以参考下
    2017-05-05
  • Qt常用容器类的使用

    Qt常用容器类的使用

    本文主要介绍了Qt常用容器类的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • 浅析C++中的间接宏函数

    浅析C++中的间接宏函数

    这篇文章主要介绍了C++中的间接宏函数,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • C++检测IPv4和IPv6地址合法性的两种方法

    C++检测IPv4和IPv6地址合法性的两种方法

    本文介绍了两种在C++中验证IPv4和IPv6地址合法性的方法,推荐使用系统原生接口inet_pton函数,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2026-01-01
  • C++的sstream标准库详细介绍

    C++的sstream标准库详细介绍

    以下是对C++中的的sstream标准库进行了详细的介绍,需要的朋友可以过来参考下
    2013-09-09
  • ACE反应器(Reactor)模式的深入分析

    ACE反应器(Reactor)模式的深入分析

    本篇文章是对ACE反应器(Reactor)模式进行了详细的分析介绍,需要的朋友参考下
    2013-05-05

最新评论