在现代C++(C++17及以后)中,std::variant提供了一种类型安全的“和类型”(union)实现,能够在编译期保证只存储合法的值。利用它,我们可以构建一个既安全又易于维护的状态机。本文将通过一个“交通灯”状态机的例子,演示如何使用std::variant、std::visit以及std::chrono来实现一个简单的、可扩展的状态机。
1. 状态机概念回顾
状态机由一组状态、事件和转换组成。传统实现往往用枚举或字符串来表示状态,再用switch语句写转换逻辑。这样做容易出错:一旦状态变化,需要手动维护多个switch,难以保证类型安全,也不易扩展。
2. std::variant 的优势
| 特点 | 传统实现 | std::variant 实现 |
|---|---|---|
| 类型安全 | 仅靠枚举,易出错 | 编译时检查合法类型 |
| 可维护性 | 需要手动维护转换表 | 通过结构化访问简化 |
| 可扩展性 | 添加状态需要改多个文件 | 只需新增结构体 |
3. 示例:交通灯状态机
3.1 状态定义
#include <variant>
#include <iostream>
#include <chrono>
#include <thread>
#include <string>
struct Red { int duration; }; // 红灯持续时间(秒)
struct Green { int duration; }; // 绿灯持续时间(秒)
struct Yellow { int duration; }; // 黄灯持续时间(秒)
struct Off {}; // 灯关闭状态
using LightState = std::variant<Red, Green, Yellow, Off>;
3.2 状态转换函数
LightState next_state(const LightState& current) {
return std::visit([](auto&& state) -> LightState {
using T = std::decay_t<decltype(state)>;
if constexpr (std::is_same_v<T, Red>) return Green{ state.duration };
else if constexpr (std::is_same_v<T, Green>) return Yellow{ state.duration };
else if constexpr (std::is_same_v<T, Yellow>) return Red{ state.duration };
else /* Off */ return Red{ 5 }; // 开灯默认红灯
}, current);
}
3.3 状态展示
std::string state_to_string(const LightState& state) {
return std::visit([](auto&& s) -> std::string {
using T = std::decay_t<decltype(s)>;
if constexpr (std::is_same_v<T, Red>) return "红灯";
else if constexpr (std::is_same_v<T, Green>) return "绿灯";
else if constexpr (std::is_same_v<T, Yellow>) return "黄灯";
else /* Off */ return "关闭";
}, state);
}
3.4 运行循环
int main() {
LightState current = Off{};
for (int i = 0; i < 10; ++i) {
std::cout << "当前状态: " << state_to_string(current) << std::endl;
int delay = std::visit([](auto&& s) -> int {
using T = std::decay_t<decltype(s)>;
if constexpr (std::is_same_v<T, Off>) return 1;
else return s.duration;
}, current);
std::this_thread::sleep_for(std::chrono::seconds(delay));
current = next_state(current);
}
return 0;
}
运行后输出示例(假设所有状态持续 5 秒):
当前状态: 关闭
当前状态: 红灯
当前状态: 绿灯
当前状态: 黄灯
当前状态: 红灯
...
4. 关键点说明
-
类型安全
std::variant在编译期保证只能存放预定义的状态结构,任何非法状态都会导致编译错误。 -
可维护
通过std::visit集中处理不同状态,无需多处switch,新增状态只需添加结构体并在visit中处理。 -
可扩展
如果想为每个状态添加更多信息(如亮度、颜色编码),只需在相应结构体中添加成员,其他代码不受影响。 -
延迟控制
用std::chrono和std::this_thread::sleep_for实现真实时间延迟,便于演示与调试。
5. 小结
利用 std::variant 与 std::visit,我们可以轻松构建一个类型安全、易维护的状态机。该技术适用于任何需要在有限状态集合之间转换的场景,例如设备控制、游戏状态管理或协议解析。只要遵循“状态结构体 + 访问器 + 转换函数”的模式,即可快速实现稳健的状态机。
祝你编码愉快!