C++ std::variant的std::monostate作为默认无值状态
C++17 引入了 std::variant,作为类型安全的 union 替代品。然而,与可以指向 nullptr 的指针或具有 std::nullopt 的 std::optional 不同,std::variant 必须始终持有其类型列表中的某一个值。当你需要一个“尚未赋值”或“无效”的状态时,使用 std::monostate 是标准且最高效的解决方案。以下步骤将演示如何定义、初始化及操作包含 std::monostate 的 std::variant。
1. 理解默认构造的限制
尝试直接定义一个不含默认构造函数类型的 variant 会导致编译错误。例如,std::variant<int, MyClass> 要求 MyClass 可以被默认构造,或者你在构造时必须显式提供值。为了绕过这个限制并允许“空值”状态,标准库提供了一个名为 std::monostate 的空类型。
2. 包含必要的头文件
打开你的源代码文件,添加以下头文件以确保 std::variant、std::monostate 和输入输出流可用。
#include <iostream>
#include <variant>
#include <string>
3. 定义包含 std::monostate 的 Variant
声明一个新的 variant 类型,将 std::monostate 放在模板参数列表的第一位。
using MyVariant = std::variant<std::monostate, int, std::string>;
将 std::monostate 作为第一个参数有两个好处:首先,它允许 MyVariant 进行默认构造(默认即 std::monostate);其次,它通常对内存布局最友好。
4. 初始化并检查状态
实例化 MyVariant 对象。由于我们使用了 std::monostate,不需要显式传递初始化值。使用 std::holds_alternative 来判断当前是否处于“空”状态。
int main() {
MyVariant data; // 默认初始化为 std::monostate
// 检查当前是否持有 std::monostate
if (std::holds_alternative<std::monostate>(data)) {
std::cout << "Data is currently empty (Monostate)." << std::endl;
}
}
5. 赋值有效数据并切换状态
执行赋值操作,将状态从 std::monostate 切换到具体的业务类型(如 int 或 std::string)。
// 赋值整数,状态切换为 int
data = 42;
if (std::holds_alternative<int>(data)) {
std::cout << "Data is now int: " << std::get<int>(data) << std::endl;
}
// 赋值字符串,状态切换为 string
data = "Hello, C++17";
6. 在 std::visit 中处理 std::monostate
使用 std::visit 遍历 variant 时,必须提供一个针对 std::monostate 的处理分支。利用 if constexpr(C++17 特性)可以在编译时根据具体类型生成不同的逻辑。
std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, std::monostate>) {
std::cout << "Visitor: State is empty." << std::endl;
}
else if constexpr (std::is_same_v<T, int>) {
std::cout << "Visitor: Got integer " << arg << std::endl;
}
else if constexpr (std::is_same_v<T, std::string>) {
std::cout << "Visitor: Got string " << arg << std::endl;
}
}, data);
7. 重置 Variant 为空状态
赋值一个空的 std::monostate 实例(或者直接使用 {}),可以将 variant 重置回初始的无值状态。
// 重置为默认状态
data = std::monostate{};
if (std::holds_alternative<std::monostate>(data)) {
std::cout << "Data has been reset to empty." << std::endl;
}
return 0;
}
对比分析:std::optional 与 std::monostate
在选择数据结构时,参考下表区分 std::optional 和包含 std::monostate 的 std::variant 的适用场景。
| 特性 | std::optional |
std::variant (含 std::monostate) |
|---|---|---|
| 核心用途 | 表示一个可能存在的单一类型值 | 表示一个多态状态,可能是多种类型之一 |
| 无值表示 | std::nullopt |
std::monostate |
| 类型数量 | 固定为 0 或 1 个有效类型 | 可以是 2 个或更多有效类型 |
| 适用场景 | 函数返回值可能不存在 | 状态机、消息解析、处理异构数据 |
| 内存占用 | sizeof(T) + bool (通常) |
max(sizeof(Ts)...) + index |
状态流转示意图
下图描述了在使用 std::monostate 时,variant 内部状态随赋值操作变化的典型流程。

暂无评论,快来抢沙发吧!