不能直接无脑替换,但std::variant是绝大多数场景下更安全的union替代方案;它运行时维护类型标签并强制访问校验,自动管理构造/析构,避免未定义行为。

std::variant 能不能直接替代 union?
不能无脑替换,但绝大多数业务场景下,std::variant 是更安全、更省心的选择。裸 union 不记录当前存的是哪个类型,也不调用构造/析构函数;而 std::variant 在运行时维护一个活跃索引(tag),每次访问前强制校验——这是它能避免未定义行为的根本原因。
常见错误现象:union { std::string s; int i; } u; 编译可能通过,但 u.s = "hello"; printf("%d", u.i); 会崩溃或输出垃圾值;而 std::variant<:string int> v = "hello"; std::get<int>(v);</int></:string> 直接抛出 std::bad_variant_access 异常,问题暴露得早、可控。
- 适用:配置字段、AST节点、协议解析、状态机中的“非此即彼”数据容器
- 不适用:需要与 C ABI 对齐的共享内存结构体、极致内存敏感场景(
std::variant至少多占 1 字节 tag) - 注意:默认构造会尝试构造第一个类型;若首类型不可默认构造(如
std::string),整个std::variant就不可默认构造
怎么声明和赋值 std::variant?
声明时列出所有允许的类型,赋值时直接用 =,它会自动销毁旧值、构造新值,无需手动管理生命周期。
最简可用写法:
立即学习“C++免费学习笔记(深入)”;
std::variant<int, std::string, double> v; v = 42; // 活跃类型变为 int v = "ok"; // 自动析构 int,构造 std::string v = 3.14; // 自动析构 std::string,构造 double
- 支持类模板参数推导(C++17):
auto v = std::variant{42};→ 推导为std::variant<int></int> - 若需空状态,把
std::monostate放在首位:std::variant<:monostate int std::string> v{};</:monostate> - 赋值过程是异常安全的:若新类型的构造抛异常,旧值保持完好
怎么安全读取 std::variant 的值?
不能像 union 那样裸指针强转或直接访问成员。必须用 std::get 或 std::visit,否则编译不过或运行时报错。
-
std::get<int>(v)</int>:按类型提取,类型不匹配时抛std::bad_variant_access -
std::get(v):按索引提取(从 0 开始),越界同样抛异常 -
std::get_if<double>(&v)</double>:返回double*,若类型不匹配则返回nullptr,适合不确定类型时的非抛出访问 -
std::holds_alternative<:string>(v)</:string>:先判断再取,配合std::get使用更清晰
推荐优先用 std::visit,尤其当分支逻辑较复杂时:
std::visit([](const auto& x) {
using T = std::decay_t<decltype(x)>;
if constexpr (std::is_same_v<T, int>) {
std::cout << "int: " << x << "\n";
} else if constexpr (std::is_same_v<T, std::string>) {
std::cout << "string: " << x << "\n";
}
}, v);
absl::variant 和 std::variant 有什么区别?
本质是同一套机制,absl::variant 是 Abseil 对 std::variant 的封装,主要差异在接口细节和兼容性上。
- 语法几乎一致:
absl::variant<int std::string> v;</int>→v = "hello"; -
absl::holds_alternative/absl::get/absl::get_if行为与标准版相同,但部分版本对constexpr支持更早 - Abseil 版本通常更激进地启用 C++17+ 特性,如果你项目已用 Abseil,且需更好调试支持或更细粒度错误提示,可优先选它
- 纯标准库项目就用
std::variant,无需额外依赖
真正容易被忽略的点是:无论用哪个,std::variant 的 tag 是运行时存储的,不是编译期常量;所以 switch on type 只能在运行时做,没法靠 template specialization 完全消除分支开销——这点在高频循环里要心里有数。


















