std::variant要求类型列表中每个类型可构造可析构且互不重复,禁止void、引用、数组;访问需用std::visit确保全覆盖或先std::holds_alternative再std::get;内存含索引字段非零开销;赋值构造应优先move避免深拷贝。

std::variant 用错类型列表会编译失败
直接写 std::variant<int int></int> 或包含引用、void、数组类型的列表,编译器会拒绝——它要求每个类型必须可构造、可析构,且两两不重复。
常见错误是误把 std::string& 塞进去,或漏掉 std::monostate 作为“空状态”占位符(尤其需要默认构造时)。
- 合法写法:
std::variant<:monostate int std::string></:monostate>(推荐开头放std::monostate) - 非法写法:
std::variant<int void></int>→ 编译报错:error: 'void' is not a valid type for variant - 如果想存指针或引用,改用
std::variant<int std::string></int>,而非引用类型
访问 variant 必须处理所有可能类型
std::get<t>(v)</t> 是最危险的访问方式:运行时若当前值不是 T 类型,会抛出 std::bad_variant_access。别指望它像 Python 的 getattr 那样静默失败。
- 安全做法优先用
std::visit+ lambda,编译期确保覆盖全部分支:std::visit([](auto&& arg) { using T = std::decay_t<decltype(arg)>; if constexpr (std::is_same_v<T, int>) { /* handle int */ } else if constexpr (std::is_same_v<T, std::string>) { /* handle string */ } }, v); - 若确定类型,用
std::holds_alternative<t>(v)</t>先判断再std::get<t>(v)</t> - 注意:lambda 中的
auto&&是万能引用,std::decay_t和if constexpr是关键,否则模板实例化会失败
std::variant 和 union 的内存布局差异直接影响性能
std::variant 内部按最大成员对齐并预留足够空间,但额外携带一个 index 字段(通常 1–4 字节),总大小 ≥ max(sizeof(Ts)...) + sizeof(index)。它不是零开销抽象。
- 比如
std::variant<char double></char>在 x64 上通常是 16 字节(double占 8 字节 + 对齐 + index),而手工union可以压到 8 字节 - 频繁读写小对象(如
int/bool)时,std::variant的分支预测开销和index检查可能比if-else链慢 - 若只读场景多、类型数少,考虑用
std::variant;若极致控制内存/速度,且能手动管理生命周期,union+ 枚举仍是选项
移动语义在 variant 赋值时容易被忽略
给 std::variant 赋新值时,默认调用对应类型的拷贝构造;但若源对象是右值,应显式 move,否则可能触发深拷贝(尤其对 std::string、std::vector 等)。
立即学习“C++免费学习笔记(深入)”;
- 错误:
v = str;→ 拷贝整个字符串 - 正确:
v = std::move(str);→ 移动后str处于有效但未定义状态 - 构造时也一样:
std::variant<:string int> v{std::move(s)};</:string>比v{s}更高效 - 注意:move 后原变量不能再安全使用,调试时容易因忘记初始化导致崩溃
类型安全是有代价的——std::variant 的编译期检查、运行时索引、访问路径分支,每一步都藏在看似简单的接口后面。用之前先问自己:是否真需要这种类型擦除,还是只是想避免继承?


















