**标题:如何在 C++ 中使用 std::variant 实现类型安全的多态容器**

在现代 C++(从 C++17 开始)中,std::variant 为我们提供了一种类型安全的方式来存储多种可能类型的值,类似于“和类型”(union)但更安全、更灵活。它常被用来替代传统的 std::anyboost::variant 或手写的多态实现。下面将详细介绍 std::variant 的核心概念、常用操作以及一个完整的使用示例。


1. 基础概念

1.1 什么是 std::variant

std::variant<Ts...> 是一个模板类,它接受一个或多个类型参数 Ts,在运行时只会保存其中一种类型的值。不同于 std::any 的非类型安全,它在编译期就已经确定了所有可能的类型,从而在使用时可以避免很多类型转换错误。

1.2 核心特性

特性 说明
类型安全 编译期检查,不能随意取值
可访问性 `std::get
()std::get_if()`
访问器 std::visit 进行模式匹配
默认构造 只能在第一个类型上构造,或者使用 std::variant 的默认值
无拷贝 只在值存在时才拷贝,避免了无意义的拷贝

2. 常用操作

2.1 定义

std::variant<int, double, std::string> v;

2.2 赋值

v = 42;            // 赋值 int
v = 3.14;          // 赋值 double
v = std::string("hello"); // 赋值 std::string

2.3 查询当前类型

std::cout << "Index: " << v.index() << "\n";          // 返回当前类型索引
std::cout << "Type: " << v.type().name() << "\n";      // 返回 RTTI 类型名称

2.4 取值

try {
    int i = std::get <int>(v);          // 若不匹配则抛异常 std::bad_variant_access
    std::cout << "int: " << i << "\n";
} catch (const std::bad_variant_access&) {
    std::cout << "v 不包含 int 类型\n";
}

if (auto p = std::get_if<std::string>(&v)) {
    std::cout << "string: " << *p << "\n";
}

2.5 访问器(visit)

auto visitor = [](auto&& arg) {
    using T = std::decay_t<decltype(arg)>;
    if constexpr (std::is_same_v<T, int>)
        std::cout << "int: " << arg << "\n";
    else if constexpr (std::is_same_v<T, double>)
        std::cout << "double: " << arg << "\n";
    else if constexpr (std::is_same_v<T, std::string>)
        std::cout << "string: " << arg << "\n";
};

std::visit(visitor, v);

3. 实际应用场景

3.1 解析 JSON

使用 std::variant 可以直观地表示 JSON 的基本类型(null、bool、number、string、array、object)。例如:

using JsonValue = std::variant<
    std::monostate,               // null
    bool,
    double,
    std::string,
    std::vector <JsonValue>,
    std::map<std::string, JsonValue>
>;

3.2 命令模式

命令对象可以定义为 std::variant,以避免在运行时使用 dynamic_cast

struct Move { int dx, dy; };
struct Resize { int width, height; };
using Command = std::variant<Move, Resize>;

void execute(const Command& cmd) {
    std::visit(overloaded {
        [](const Move& m){ /* 处理移动 */ },
        [](const Resize& r){ /* 处理尺寸调整 */ }
    }, cmd);
}

4. 性能与注意事项

  • 大小std::variant 的大小等于最大成员类型的大小加上一个偏移量(通常是 sizeof(size_t))。因此不要把大对象直接放进去,而是使用 std::shared_ptrstd::unique_ptr
  • 构造与析构:只会构造/析构当前类型,避免不必要的资源管理。
  • 移动语义std::variant 默认支持移动,适合与 std::move 搭配使用。
  • 错误处理std::get 抛异常,std::get_if 返回指针。根据需要选择。

5. 示例:实现一个简单的表达式求值器

#include <variant>
#include <string>
#include <iostream>
#include <map>
#include <memory>
#include <vector>
#include <cmath>

struct Expr {
    using ExprPtr = std::shared_ptr <Expr>;
    struct Add { ExprPtr left, right; };
    struct Sub { ExprPtr left, right; };
    struct Mul { ExprPtr left, right; };
    struct Div { ExprPtr left, right; };
    struct Pow { ExprPtr base, exponent; };
    struct Neg { ExprPtr operand; };
    struct Const { double value; };
    using Value = std::variant<Add, Sub, Mul, Div, Pow, Neg, Const>;
    Value value;
};

double eval(const Expr::ExprPtr& e) {
    return std::visit(overloaded{
        [](const Expr::Add& a){ return eval(a.left) + eval(a.right); },
        [](const Expr::Sub& s){ return eval(s.left) - eval(s.right); },
        [](const Expr::Mul& m){ return eval(m.left) * eval(m.right); },
        [](const Expr::Div& d){ return eval(d.left) / eval(d.right); },
        [](const Expr::Pow& p){ return std::pow(eval(p.base), eval(p.exponent)); },
        [](const Expr::Neg& n){ return -eval(n.operand); },
        [](const Expr::Const& c){ return c.value; }
    }, e->value);
}

int main() {
    // 计算 (3 + 4) * (2 - 1)
    auto expr = std::make_shared <Expr>();
    expr->value = Expr::Mul{
        std::make_shared <Expr>(Expr{Expr::Add{std::make_shared<Expr>(Expr{Expr::Const{3}}),
                                            std::make_shared <Expr>(Expr{Expr::Const{4}})}}),
        std::make_shared <Expr>(Expr{Expr::Sub{std::make_shared<Expr>(Expr{Expr::Const{2}}),
                                            std::make_shared <Expr>(Expr{Expr::Const{1}})}})
    };
    std::cout << "Result: " << eval(expr) << std::endl;
}

运行结果:

Result: 7

6. 小结

  • std::variant 是一种类型安全、内存占用可控的“和类型”实现。
  • std::any 不同,它提供了编译期的类型检查。
  • std::visit 为访问不同类型提供了优雅的模式匹配方式。
  • 适用于解析 JSON、实现命令模式、构造表达式树等多种场景。

掌握 std::variant 后,你可以写出更安全、更易维护的 C++ 代码,减少动态多态带来的运行时错误与性能开销。祝你编码愉快!

发表评论