一、基本概念
std::variant 是C++17引入的一个新的标准库类型,它提供了一种类型安全的联合体(Type-safe Union)。这个类可以在同一时间持有几种可能类型中的一个值。
相较于传统的 C 风格 union,std::variant 最大的优势是类型安全:它知道自己当前存的是什么类型,并在析构时正确调用对应类型的析构函数,不会发生内存泄漏或未定义行为。
std::variant 是一个模板类,可以存储几种不同类型中的一个值。它的声明如下:
1 2
| template <class... Types> class variant;
|
- 类型安全:与C风格的联合体不同,
std::variant 是类型安全的。
- 无默认构造:如果第一个类型不是默认构造的,那么
std::variant 也不是默认构造的。
- 不允许引用、数组和void:这些类型不能作为
std::variant 的可选类型。
- 可以为空:通过使用
std::monostate 作为第一个类型,std::variant 可以表示”无值”状态。
- 内存分配:完全分配在栈上,不会发生动态内存分配(Heap Allocation),因此其性能显著优于基于多态(
std::unique_ptr)或 std::any 的方案。
- 内存大小:
sizeof(std::variant) 大约为 max(sizeof(T1), sizeof(T2), sizeof(T3)) + sizeof(size_t)(因为需要一个额外的 index 标记来记录当前是哪种类型)。
二、基本用法
创建和访问:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <variant> #include <string> #include <iostream> int main() { std::variant<int, float, std::string> v; v = 42; std::cout << std::get<int>(v) << std::endl; v = 3.14f; std::cout << std::get<float>(v) << std::endl; v = "hello"; std::cout << std::get<std::string>(v) << std::endl; if (const auto intPtr = std::get_if<int>(&v)) { std::cout << "It's an int: " << *intPtr << std::endl; } else { std::cout << "It's not an int" << std::endl; } }
|
使用 std::holds_alternative 检查当前类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <variant> #include <iostream> #include <string>
void printValue(const std::variant<int, double, std::string>& v) { if (std::holds_alternative<int>(v)) { std::cout << "Int: " << std::get<int>(v) << '\n'; } else if (std::holds_alternative<double>(v)) { std::cout << "Double: " << std::get<double>(v) << '\n'; } else if (std::holds_alternative<std::string>(v)) { std::cout << "String: " << std::get<std::string>(v) << '\n'; } }
|
使用 std::visit 访问:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include <variant> #include <iostream> struct Visitor { void operator()(int i) { std::cout << "It's an int: " << i << std::endl; } void operator()(float f) { std::cout << "It's a float: " << f << std::endl; } void operator()(const std::string& s) { std::cout << "It's a string: " << s << std::endl; } }; int main() { std::variant<int, float, std::string> v = 42; std::visit(Visitor{}, v); v = 3.14f; std::visit(Visitor{}, v); v = "hello"; std::visit(Visitor{}, v); }
|
使用 std::monostate (std::monostate 可以用来表示 “无值” 状态):
1 2 3 4 5 6 7 8 9
| #include <variant> #include <iostream> int main() { std::variant<std::monostate, int, std::string> v; if (std::holds_alternative<std::monostate>(v)) { std::cout << "The variant is empty" << std::endl; } }
|
异常处理 - 当尝试访问错误的类型时,std::get 会抛出 std::bad_variant_access 异常:
1 2 3 4 5 6 7 8 9 10 11 12
| #include <variant> #include <iostream> #include <stdexcept> int main() { std::variant<int, std::string> v = 42; try { std::get<std::string>(v); } catch (const std::bad_variant_access& e) { std::cout << "Exception: " << e.what() << std::endl; } }
|
lambda 表达式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include <variant> #include <string> #include <vector> #include <iostream>
struct Connecting {}; struct Connected { std::string ip; uint16_t port; }; struct Disconnected { std::string reason; };
using ConnectionState = std::variant<Connecting, Connected, Disconnected>;
void handleState(const ConnectionState& state) { std::visit(overloaded { [](const Connecting&) { std::cout << "Connecting to server...\n"; }, [](const Connected& conn) { std::cout << "Connected to " << conn.ip << ":" << conn.port << '\n'; }, [](const Disconnected& dis) { std::cout << "Disconnected! Reason: " << dis.reason << '\n'; } }, state); }
|
三、使用注意
std::variant 在极少数情况下可能处于“无价值”状态。 例如:当你在给 variant 赋予一个新类型值时,新值的构造函数抛出了异常,此时旧的值已经被销毁,而新的值没有构造成功。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| struct BadCopy { BadCopy() = default; BadCopy(const BadCopy&) { throw std::runtime_error("Copy failed!"); } };
std::variant<int, BadCopy> v = 10; try { v = BadCopy{}; } catch (...) { if (v.valueless_by_exception()) { std::cout << "Variant is empty/invalid!\n"; } }
|
如果 std::variant 的第一个类型没有默认构造函数,那么整个 variant 无法默认构造。
- 解决方法:将
std::monostate 作为第一个类型,用作占位符(代表空值/未初始化状态)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <variant> #include <iostream>
struct NoDefault { NoDefault(int x) {} };
int main() {
std::variant<std::monostate, NoDefault, int> v2; if (std::holds_alternative<std::monostate>(v2)) { std::cout << "Variant is currently empty (monostate)\n"; } }
|