C++ std::variant

一、基本概念

std::variant 是C++17引入的一个新的标准库类型,它提供了一种类型安全的联合体(Type-safe Union)。这个类可以在同一时间持有几种可能类型中的一个值。

相较于传统的 C 风格 unionstd::variant 最大的优势是类型安全:它知道自己当前存的是什么类型,并在析构时正确调用对应类型的析构函数,不会发生内存泄漏或未定义行为。

std::variant 是一个模板类,可以存储几种不同类型中的一个值。它的声明如下:

1
2
template <class... Types>
class variant;
  1. 类型安全:与C风格的联合体不同,std::variant 是类型安全的。
  2. 无默认构造:如果第一个类型不是默认构造的,那么 std::variant 也不是默认构造的。
  3. 不允许引用、数组和void:这些类型不能作为 std::variant 的可选类型。
  4. 可以为空:通过使用 std::monostate 作为第一个类型,std::variant 可以表示”无值”状态。
  5. 内存分配完全分配在栈上,不会发生动态内存分配(Heap Allocation),因此其性能显著优于基于多态(std::unique_ptr)或 std::any 的方案。
  6. 内存大小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; // v 现在包含 int
std::cout << std::get<int>(v) << std::endl; // 输出:42

v = 3.14f; // v 现在包含 float
std::cout << std::get<float>(v) << std::endl; // 输出:3.14

v = "hello"; // v 现在包含 string
std::cout << std::get<std::string>(v) << std::endl; // 输出:hello

// 使用 std::get_if 安全地获取值
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) {
// 1. 检查当前是否持有某种类型
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::get 获取值(如果类型不匹配会抛出 std::bad_variant_access 异常)
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); // 输出:It's an int: 42

v = 3.14f;
std::visit(Visitor{}, v); // 输出:It's a float: 3.14

v = "hello";
std::visit(Visitor{}, v); // 输出:It's a string: hello
}

使用 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 (...) {
// 此时 v 可能不保存任何类型
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<NoDefault, int> v1;

// 编译成功:使用 std::monostate 充当“空”状态
std::variant<std::monostate, NoDefault, int> v2;

if (std::holds_alternative<std::monostate>(v2)) {
std::cout << "Variant is currently empty (monostate)\n";
}
}

C++ std::variant
http://example.com/2026/06/03/C++-std-variant/
作者
Yu xin
发布于
2026年6月3日
许可协议