C++中std::variant的使用详解和实战代码示例
std::variant
是 C++17 引入的一个类型安全的联合体(type-safe union),它可以在多个类型之间存储一个值,并在编译时进行类型检查。它是现代 C++ 类型擦除与泛型编程的核心工具之一,适用于构建可变类型结构、消息传递系统、状态机等。
一、基本概念
#include <variant>std::variant<T1, T2, ..., Tn> v;
- 类似于联合体
union
,但类型安全。 std::variant
只能存储其中一个类型的值。- 默认构造时,会初始化为第一个类型的默认值。
二、核心操作
1. 构造与赋值
#include <iostream>
#include <variant>int main() {std::variant<int, std::string> v;v = 42; // 存储 intstd::cout << std::get<int>(v) << std::endl;v = "hello"; // 存储 stringstd::cout << std::get<std::string>(v) << std::endl;return 0;
}
2. 访问值:std::get<T>()
或 std::get<index>()
std::get<int>(v); // 根据类型访问
std::get<0>(v); // 根据索引访问(int 是第 0 个类型)
如果类型不匹配,将抛出
std::bad_variant_access
异常。
3. 判断当前存储的类型
if (std::holds_alternative<std::string>(v)) {std::cout << "v contains a string\n";
}
4. 获取当前类型索引
std::cout << "index = " << v.index() << std::endl;
三、使用 std::visit
实现模式匹配
#include <iostream>
#include <variant>void printVariant(const std::variant<int, std::string>& v) {std::visit([](auto&& arg) {std::cout << "Value: " << arg << std::endl;}, v);
}
四、完整示例:表达式计算器
#include <iostream>
#include <variant>
#include <vector>using Expr = std::variant<int, float, std::string>;void processExpr(const Expr& e) {std::visit([](auto&& val) {using T = std::decay_t<decltype(val)>;if constexpr (std::is_same_v<T, int>)std::cout << "int: " << val << "\n";else if constexpr (std::is_same_v<T, float>)std::cout << "float: " << val << "\n";else if constexpr (std::is_same_v<T, std::string>)std::cout << "string: " << val << "\n";}, e);
}int main() {std::vector<Expr> exprs = { 1, 3.14f, "hello" };for (const auto& e : exprs)processExpr(e);return 0;
}
五、进阶用法:递归类型(std::monostate
+ std::unique_ptr
)
std::variant
不能直接定义递归类型(自身包含自身)。需要使用指针 + std::monostate
:
struct Node;using Tree = std::variant<std::monostate, // 空节点int, // 叶子std::unique_ptr<Node> // 子树
>;struct Node {Tree left;Tree right;
};
六、注意事项
项目 | 说明 |
---|---|
异常 | std::get<T>(v) 如果类型不对,会抛出 std::bad_variant_access |
默认值 | 默认初始化为第一个类型的默认构造值 |
索引 | v.index() 返回当前类型在 variant 中的索引 |
编译期匹配 | 使用 if constexpr + std::visit 可实现类型安全分派 |
七、综合实战示例
下面是一个用 std::variant
实现的 有限状态机(FSM)+ 消息分发系统 的综合示例,适用于状态驱动的系统,如游戏角色状态、UI 状态、网络连接状态等。
示例目的结构
我们实现一个简单的 FSM:角色可以在 Idle
, Moving
, Attacking
三个状态间切换,接收 Message
控制状态转换。
1. 基础定义
#include <iostream>
#include <variant>
#include <string>// 状态定义
struct Idle {};
struct Moving { std::string destination; };
struct Attacking { std::string target; };// 消息定义
struct StartMoving { std::string destination; };
struct StopMoving {};
struct StartAttack { std::string target; };
struct StopAttack {};// 状态类型和消息类型
using State = std::variant<Idle, Moving, Attacking>;
using Message = std::variant<StartMoving, StopMoving, StartAttack, StopAttack>;
2. 消息处理器(核心)
void handle_message(State& state, const Message& msg) {std::visit([&](auto&& current_state) {using Current = std::decay_t<decltype(current_state)>;std::visit([&](auto&& m) {using Msg = std::decay_t<decltype(m)>;if constexpr (std::is_same_v<Current, Idle>) {if constexpr (std::is_same_v<Msg, StartMoving>) {std::cout << "Idle → Moving to " << m.destination << "\n";state = Moving{m.destination};} else if constexpr (std::is_same_v<Msg, StartAttack>) {std::cout << "Idle → Attacking " << m.target << "\n";state = Attacking{m.target};}} else if constexpr (std::is_same_v<Current, Moving>) {if constexpr (std::is_same_v<Msg, StopMoving>) {std::cout << "Moving → Idle\n";state = Idle{};} else if constexpr (std::is_same_v<Msg, StartAttack>) {std::cout << "Moving → Attacking " << m.target << "\n";state = Attacking{m.target};}} else if constexpr (std::is_same_v<Current, Attacking>) {if constexpr (std::is_same_v<Msg, StopAttack>) {std::cout << "Attacking → Idle\n";state = Idle{};} else if constexpr (std::is_same_v<Msg, StartMoving>) {std::cout << "Attacking → Moving to " << m.destination << "\n";state = Moving{m.destination};}}}, msg);}, state);
}
3. 主程序(状态切换测试)
int main() {State state = Idle{};handle_message(state, StartMoving{"North"});handle_message(state, StartAttack{"Dragon"});handle_message(state, StopAttack{});handle_message(state, StartAttack{"Goblin"});handle_message(state, StartMoving{"East"});handle_message(state, StopMoving{});return 0;
}
输出示例:
Idle → Moving to North
Moving → Attacking Dragon
Attacking → Idle
Idle → Attacking Goblin
Attacking → Moving to East
Moving → Idle
小结:此结构的优势
特性 | 说明 |
---|---|
类型安全 | std::variant 和 std::visit 编译期确定类型,无需 RTTI 或类型转换 |
易扩展 | 增加新状态或消息只需添加结构体 + 分支处理 |
面向对象可替代 | 可替代经典面向对象状态机中的虚函数分派 |
适合嵌入式/游戏 | 无动态分配,运行时高效 |
总结
用法 | 说明 |
---|---|
std::variant<A, B> | 表示可以存储 A 或 B 类型的变量 |
std::get<T>(v) | 获取当前存储的值(类型匹配) |
std::visit() | 模式匹配:对当前值执行对应操作 |
holds_alternative<T>() | 判断当前是否存储的是 T 类型 |