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;                 // 存储 int
    std::cout << std::get<int>(v) << std::endl;

    v = "hello";            // 存储 string
    std::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::variantstd::visit 编译期确定类型,无需 RTTI 或类型转换
易扩展增加新状态或消息只需添加结构体 + 分支处理
面向对象可替代可替代经典面向对象状态机中的虚函数分派
适合嵌入式/游戏无动态分配,运行时高效

总结

用法说明
std::variant<A, B>表示可以存储 A 或 B 类型的变量
std::get<T>(v)获取当前存储的值(类型匹配)
std::visit()模式匹配:对当前值执行对应操作
holds_alternative<T>()判断当前是否存储的是 T 类型

<think>嗯,用户需要了解C++std::variant使用方法并请求示例代码。用户特别提到"使用指南""示例",说明需要实用教程。查看系统记录注意到用户之前提供的两份站内引用:引用[1]展示基础用法异常处理,引用[2]则强调访问者模式的高级应用。用户的上次询问聚焦std::variant整体使用,这次可深化为完整技术指南。准备设计结构清晰的响应方案:先简述概念价值,再用基础代码示范类型安全访问,接着引入访问者模式解决多类型处理,最后覆盖高级场景。每个示例都需包含错误处理,特别是std::bad_variant_access的捕获。用户可能是中级C++开发者,代码示例会采用C++17标准。需要特别注意:1.所有代码标记为C++(原引用误标Python)2.变量命名用vVar保持一致性3.访问者示例需演示lambda重载4.补充monostate处理空状态的技巧最后考虑列出典问题:异常处理、性能、类型转换等,覆盖用户后续可能疑问。响应结尾留出提问空间,因为variant的索引访问或类型判断可能也是用户潜在需求。</think> 以下是C++ `std::variant` 的使用指南示例详解: --- ### **一、基本概念** `std::variant`(C++17引入)是一种类型安全的联合体,可存储指定类型集合中的任意一种值。核心特点: 1. 类型安全访问(替代`union`) 2. 运行时自动追踪当前存储的类型 3. 支持访问者模式 --- ### **二、基础使用示例** #### **1. 创建与赋值** ```cpp #include <variant> #include <string> std::variant<int, std::string, double> v; // 默认初始化存首类型(int) v = 3.14; // 转为存储double v = "Hello"; // 转为存储string ``` #### **2. 安全访问值** ```cpp // 方法1:std::get<类型> try { double num = std::get<double>(v); // 成功获取 } catch (const std::bad_variant_access& e) { std::cerr << "Type error: " << e.what(); } // 方法2:std::get<索引> try { auto str = std::get<1>(v); // 获取第2个类型(string) } catch (...) { /* 处理异常 */ } ``` --- ### **三、高级访问方法** #### **1. 访问者模式 (std::visit)** ```cpp auto visitor = [](auto&& arg) { if constexpr (std::is_same_v<decltype(arg), int>) { std::cout << "Integer: " << arg; } else if constexpr (std::is_same_v<decltype(arg), double>) { std::cout << "Double: " << arg; } }; std::visit(visitor, v); // 自动匹配类型执行操作 ``` #### **2. 检查当前存储类型** ```cpp if (std::holds_alternative<int>(v)) { std::cout << "Stores int: " << std::get<int>(v); } else if (v.index() == 1) { // 索引位置检查 std::cout << "Stores string"; } ``` --- ### **四、特殊场景处理** #### **1. 避免默认构造问题** 使用`std::monostate`处理无默认构造的类型: ```cpp struct NonDefault { NonDefault(int) {} }; std::variant<std::monostate, NonDefault> v; v = NonDefault(42); // 显式赋值 ``` #### **2. 修改值时的异常安全** ```cpp // 使用emplace直接构造新值 v.emplace<std::string>("New value"); // 避免临时对象异常风险 ``` --- ### **五、完整综合示例** ```cpp #include <iostream> #include <variant> #include <string> int main() { std::variant<int, std::string, double> v = 100; // 访问演示 std::visit([](auto&& arg) { std::cout << "Value: "; using T = std::decay_t<decltype(arg)>; if constexpr (std::is_same_v<T, int>) std::cout << "INT=" << arg; else if constexpr (std::is_same_v<T, double>) std::cout << "DOUBLE=" << arg; else if constexpr (std::is_same_v<T, std::string>) std::cout << "STRING=" << arg; }, v); // 类型修改验证 v = 3.1415; std::cout << "\nCurrent index: " << v.index(); // 输出 2 return 0; } ``` --- ### **六、常见问题解答** **Q1: 何时会抛出`std::bad_variant_access`?** A:当调用`std::get<T>()`但当前存储类型非`T`时抛出[^1]。 **Q2: 性能开销如何?** A:相比`union`有轻微额外存储(记录当前类型),但访问为O(1)复杂度。 **Q3: 支持嵌套吗?** A:是!例如:`std::variant<int, std::variant<char, bool>>`。 **Q4: 如何判空?** A:用`std::monostate`占位或通过`valueless_by_exception()`检测异常状态。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

点云SLAM

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值