TinyFSM 是一个头文件式的C++11有限状态机库,以极简设计、零依赖、高性能著称,特别适合资源受限或实时性要求高的场景。它没有复杂的配置文件,所有逻辑都在代码中完成。

📦 一、安装与集成

TinyFSM 是纯头文件库,只需两步即可使用:

  1. 获取源码

    git clone https://github.com/digint/tinyfsm.git
    
  2. 包含头文件
    tinyfsm/include 目录加入项目包含路径,在代码中引入:

    #include "tinyfsm.hpp"
    

    编译时添加 -std=c++11-I 参数即可。

🧱 二、核心概念与架构

TinyFSM 的代码结构遵循事件→状态机→状态→转换的清晰脉络:

事件 (Event)       ——派生自→   tinyfsm::Event
状态机基类         ——派生自→   tinyfsm::Fsm<自身>
具体状态类         ——派生自→   状态机基类
状态转换           ——通过→     transit<目标状态>()
初始状态定义       ——通过→     FSM_INITIAL_STATE(基类, 初始状态)
事件分发           ——通过→     dispatch(事件) / FsmList::dispatch()

关键设计理念

  • 事件驱动:状态迁移由事件触发,事件可携带参数
  • 当前状态唯一:事件只会被当前活动状态处理,而非广播给所有状态
  • 编译时多态:优先使用非虚函数提升性能,虚函数仅在有继承重写需求时使用
  • 避免循环依赖:状态机间事件流向应保持单向

🚀 三、五步构建状态机

第1步:定义事件

事件类继承自 tinyfsm::Event,可携带任意成员数据:

struct SwitchEvent : tinyfsm::Event { };  // 无参事件

struct EvMove : tinyfsm::Event {
    int target_floor;                     // 带数据的事件
    EvMove(int floor) : target_floor(floor) { }
};

第2步:定义状态机基类

继承 tinyfsm::Fsm<自身>,声明所有状态共有的事件响应接口:

class SwitchFSM : public tinyfsm::Fsm<SwitchFSM> {
public:
    /* 默认处理:未定义的事件直接忽略 */
    void react(tinyfsm::Event const &) { }
    
    /* 需各状态实现的事件响应(虚函数允许重写) */
    virtual void react(SwitchEvent const &) = 0;
    virtual void react(EvMove const &) = 0;
    
    /* 进入/退出动作(默认为空) */
    virtual void entry() { }
    virtual void exit()  { }
    
    /* 可选:获取当前状态标识(便于调试) */
    virtual std::string getStateName() const = 0;
};

性能提示:所有状态响应一致的事件(如报警),声明为非虚函数可避免vtable查找。

第3步:定义具体状态类

每个状态继承自状态机基类,实现特定的事件响应和进入/退出动作:

/* 状态:关闭 */
class OffState : public SwitchFSM {
public:
    void entry() override {
        std::cout << ">> 进入状态: 关闭" << std::endl;
    }
    void exit() override {
        std::cout << "<< 离开状态: 关闭" << std::endl;
    }
    void react(SwitchEvent const &) override {
        std::cout << "  收到切换事件 -> 开" << std::endl;
        transit<OnState>();  // ⚠️ 必须是最后一行
    }
    void react(EvMove const &) override { /* 忽略 */ }
    std::string getStateName() const override { return "Off"; }
};

/* 状态:开启 */
class OnState : public SwitchFSM {
public:
    void entry() override {
        std::cout << ">> 进入状态: 开启" << std::endl;
    }
    void react(SwitchEvent const &) override {
        std::cout << "  收到切换事件 -> 关" << std::endl;
        transit<OffState>();
    }
    void react(EvMove const & e) override {
        std::cout << "  移动至楼层: " << e.target_floor << std::endl;
    }
    std::string getStateName() const override { return "On"; }
};

两条铁律

  1. transit<>() 必须是反应函数中的最后一条语句(后续代码不会执行)
  2. 禁止在 entry() / exit() 内调用 transit<>()

第4步:设置初始状态

使用宏 FSM_INITIAL_STATE(状态机基类, 初始状态类)

FSM_INITIAL_STATE(SwitchFSM, OffState)

这行代码必须在全局作用域(任何函数之外)。

第5步:启动与事件分发

int main() {
    SwitchFSM::start();              // 启动状态机(调用初始状态的entry)
    
    SwitchFSM::dispatch(SwitchEvent());  // 分发事件:关→开
    SwitchFSM::dispatch(SwitchEvent());  // 开→关
    SwitchFSM::dispatch(EvMove(5));      // 当前状态(关)忽略移动事件
    
    return 0;
}

🔄 四、高级特性

1. 带动作/条件的转换

transit<>() 支持转换动作(lambda)和条件判断(返回bool的callable):

void react(CallEvent const & e) override {
    if (e.floor == current_floor) return;
    
    // 转换动作:决定电梯上行还是下行
    auto action = [this, &e] {
        if (e.floor > current_floor)
            send_event(MotorUp());
        else
            send_event(MotorDown());
    };
    
    // 条件函数(可选):只有门关闭时才能移动
    auto condition = [] { return is_door_closed(); };
    
    transit<MovingState>(action, condition);
}

transit<>() 的执行顺序:

  1. 调用当前状态的 exit()
  2. 执行转换动作(若提供)
  3. 切换到新状态
  4. 调用新状态的 entry()

2. 多状态机协同(FsmList)

当系统包含多个状态机时(如电梯的轿厢和马达),使用 FsmList 统一初始化、复位、分发事件

/* 声明两个状态机 */
class MotorFSM : public tinyfsm::Fsm<MotorFSM> { ... };
class ElevatorFSM : public tinyfsm::Fsm<ElevatorFSM> { ... };

/* 定义状态机列表 */
using FsmHandle = tinyfsm::FsmList<MotorFSM, ElevatorFSM>;

/* 全局事件分发函数 */
template<typename E>
void send_event(E const & ev) {
    FsmHandle::template dispatch<E>(ev);
}

int main() {
    FsmHandle::start();      // 启动所有状态机
    FsmHandle::reset();      // 重置所有状态机
    send_event(Alarm());     // 同时通知马达和电梯
}

⚠️ 设计约束:事件应保持单向流动(如电梯→马达),避免循环依赖。

3. 自定义复位初始化

若状态机持有内部变量,需在复位时重置,可重写 reset() 静态方法:

class SwitchFSM : public tinyfsm::Fsm<SwitchFSM> {
public:
    static void reset() {
        // 复位所有状态实例(若使用了状态内静态变量)
        tinyfsm::StateList<OffState, OnState>::reset();
        // 重置自定义变量
        toggle_count = 0;
    }
    
    static unsigned int toggle_count;  // 需在cpp文件中定义
};

4. Mealy 与 Moore 风格

TinyFSM 同时支持两种经典状态机模型:

  • Moore型:输出仅依赖于当前状态(在 entry() 中执行动作)
  • Mealy型:输出依赖于状态+事件(在 react() 中直接执行动作)

上述开关示例分别演示了两种风格。

❗ 五、常见陷阱与最佳实践

陷阱1:误以为事件会广播给所有状态

事实dispatch() 仅将事件发送给当前活动状态。要同时触发多个状态机,需使用 FsmList

陷阱2:在转换后编写代码

void react(Event const &) {
    transit<NextState>();
    cleanup();   // ❌ 永远不会执行!
}

解决:将后续逻辑放入 entry() 或转换动作中。

陷阱3:在 entry/exit 中调用 transit

后果:可能导致状态栈混乱、无限递归。若需进入时立即转换,应重新审视状态设计

最佳实践速查表

需求做法
事件带参数在事件类中添加成员变量
状态间共享数据将数据定义为状态机基类的静态成员
调试输出状态在基类定义 virtual getStateName()
忽略大量事件基类定义 void react(tinyfsm::Event const &) { }
同一状态机复用将状态类定义为模板
条件转换使用带condition参数的 transit<>()
性能极致优化所有状态无重写的函数声明为非虚

TinyFSM 的精髓在于用最直接的C++语法表达状态图。掌握上述五步流程与三点核心约束,即可轻松驾驭从简单开关到复杂多机协同的各种状态机场景。官方示例中的 elevator 项目是理解多状态机协作的最佳起点。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐