在C++17引入的std::variant类型中,std::monostate是一个特殊的空类型,专门用来作为variant的占位符,解决variant需要表示空状态的问题。当variant的候选类型列表中没有适合表示“无数据”的类型时,加入std::monostate就可以让variant拥有一个合法的空值状态。

std::monostate的基本作用
std::monostate本身是一个空类,没有成员变量和成员函数,它的唯一实例就是默认的构造状态。当把std::monostate作为std::variant的候选类型之一时,variant就可以默认构造出一个处于空状态的对象,不需要为其他候选类型提供默认值,也不会出现歧义。
比如我们有一个variant需要存储int、string两种类型,同时需要支持空状态,如果不使用std::monostate,直接给variant加一个空类型需要自己定义,而用std::monostate就可以直接使用标准库提供的类型,避免重复造轮子。
基础使用示例
首先看一个简单的使用案例,演示如何定义包含std::monostate的variant,以及如何判断variant是否处于空状态:
#include <iostream>
#include <variant>
#include <string>
// 定义包含空状态、int、string的variant
using MyVariant = std::variant<std::monostate, int, std::string>;
int main() {
// 默认构造的variant处于monostate状态,即空状态
MyVariant var1;
// 判断variant当前是否为空状态
if (std::holds_alternative<std::monostate>(var1)) {
std::cout << "var1当前为空状态" << std::endl;
}
// 给variant赋值int类型
var1 = 100;
if (std::holds_alternative<int>(var1)) {
std::cout << "var1当前存储int值: " << std::get<int>(var1) << std::endl;
}
// 重置为空白状态
var1 = std::monostate{};
if (std::holds_alternative<std::monostate>(var1)) {
std::cout << "var1已重置为空状态" << std::endl;
}
return 0;
}
上面的代码中,MyVariant的第一个候选类型是std::monostate,因此默认构造的var1就会处于空状态。通过std::holds_alternative<std::monostate>可以判断variant当前是否为空,赋值std::monostate{}就可以把variant重置为空白状态。
为什么需要std::monostate而不是用其他类型占位
有些开发者可能会想,为什么不直接用int或者void*之类的类型作为空占位符?主要有以下几个原因:
- std::monostate是标准库专门定义的空类型,语义明确,看到它就知道是用来表示variant的空状态,代码的可读性更高。
- 如果用int作为占位符,那么variant无法区分是存储了业务上的int值0,还是表示空状态,会产生歧义。
- std::monostate的大小和普通的空类一样,不会额外增加variant的内存开销,性能上没有额外负担。
- 如果variant的其他候选类型中没有默认构造的类型,没有std::monostate的话,variant就无法默认构造,必须手动提供一个初始值,而加入std::monostate后就可以默认构造了。
和其他空状态处理方式的对比
除了使用std::monostate,还有一些其他方式可以实现variant的空状态,我们来对比一下:
| 方式 | 优点 | 缺点 |
|---|---|---|
| 使用std::monostate | 语义明确,标准库支持,无歧义,不增加额外开销 | 需要C++17及以上版本支持 |
| 自定义空结构体 | 兼容低版本C++ | 需要自己定义,语义不如标准类型清晰,增加代码量 |
| 用std::optional包裹variant | 可以表达“无variant”的状态,语义更明确 | 多了一层包装,使用起来更繁琐,内存开销更大 |
注意事项
使用std::monostate的时候需要注意以下几点:
- std::monostate必须作为variant的候选类型之一才能发挥作用,不能直接单独使用来表示variant的空状态。
- 如果需要频繁判断variant是否为空,建议把std::monostate放在variant候选类型的第一个位置,这样默认构造的variant就是空状态,不需要额外赋值。
- 访问variant的值时,如果当前是std::monostate状态,调用
std::get<其他类型>会抛出std::bad_variant_access异常,因此访问前一定要先判断当前的类型。
复杂场景示例
下面是一个更贴近实际开发的场景,用variant存储不同的操作结果,空状态表示操作还未执行:
#include <iostream>
#include <variant>
#include <string>
#include <vector>
// 操作结果类型:空状态、成功返回字符串、失败返回错误信息
using OperationResult = std::variant<std::monostate, std::string, std::string>;
// 模拟执行操作
OperationResult do_operation(bool is_success) {
if (!is_success) {
return "操作失败,参数错误";
}
return "操作成功,返回数据";
}
int main() {
OperationResult res; // 默认是空状态,表示操作未执行
// 判断操作是否执行
if (std::holds_alternative<std::monostate>(res)) {
std::cout << "操作还未执行" << std::endl;
}
// 执行操作
res = do_operation(true);
// 访问结果,先判断类型
if (std::holds_alternative<std::string>(res)) {
// 这里需要根据业务逻辑区分是成功还是失败,实际开发中可以用不同的类型区分
std::cout << "操作结果: " << std::get<std::string>(res) << std::endl;
}
return 0;
}
在这个场景中,std::monostate清晰地区分了操作未执行和操作已执行的状态,避免了用其他类型表示空状态带来的歧义问题。
std::monostatestd::variantC++空状态占位符类型修改时间:2026-06-16 08:33:38