std::any用于运行时未知类型的单值存储,依赖动态类型检查;std::variant适用于编译期已知有限类型的高效安全切换,需全覆盖处理。选any当类型完全不确定,选variant当类型集合固定且需高性能分发。

std::any 能存任意类型,但取值前必须知道类型
它像一个带类型擦除的“万能盒子”,运行时才检查类型是否匹配。和 void* 不同,std::any 自带类型信息,不会让你在解引用时崩溃——但如果你猜错了类型,std::any_cast 会抛出 std::bad_any_cast 异常。
常见错误现象:std::any_cast<int>(a)</int> 对一个存了 double 的 std::any 调用,直接崩溃(没捕获异常);或者用 std::any_cast<const int></const> 去取一个右值,编译失败。
- 使用场景:配置项解析、插件系统中传递未知但单次确定的值(比如某个字段可能是
int或std::string,但具体哪种由 JSON 字段名决定) - 安全取值必须先用
std::any_cast<t>(&a)</t>检查是否可转,或用std::any_cast<t>(a)</t>并包 try/catch - 性能影响:每次
std::any_cast都要动态类型比对,比直接访问慢;内部可能堆分配(小对象优化取决于实现) - 别把它当
union用——它不支持多类型共存,也不提供 switch-case 式的类型分发
std::variant 是编译期限定的“安全 union”
std::variant 在定义时就锁死了能存哪些类型,比如 std::variant<int std::string double></int>。它比原始 union 安全得多:构造、析构、赋值都自动管理活跃成员,不会出现“用 int 覆盖 string 后还调 string 析构函数”的 UB。
常见错误现象:忘记处理所有分支,std::visit 传入的 visitor 缺少对某个类型的重载,导致编译失败;或者用 std::get<t>(v)</t> 强制取值,而当前活跃类型不是 T,抛出 std::bad_variant_access。
立即学习“C++免费学习笔记(深入)”;
- 使用场景:状态机返回值(成功/失败/超时)、AST 节点类型(
BinaryOp/Literal/Identifier)、协议字段的多种可能取值 - 必须用
std::visit处理所有可能类型,推荐用 lambda 模板或std::overload辅助类组织逻辑 - 参数差异:
std::get_if<t>(&v)</t>返回指针,安全但需判空;std::get<t>(v)</t>更快但不安全,只应在确定类型后使用 - 内存布局紧凑,无堆分配,访问开销接近原生
union,但多了 1–2 字节的 type index
什么时候选 any,什么时候选 variant?
关键看“类型集合”是编译期可知,还是运行时才确定。
- 选
std::any:你根本不知道将来会塞什么类型(比如用户脚本传进来的值),且只存不频繁切换 - 选
std::variant:你知道全部可能类型,且需要高效、无异常地分发处理(比如解析器每步只产出几种固定节点) - 别用
std::any模拟枚举行为——类型太多会导致运行时检查膨胀,也难维护 - 别用
std::variant存 “任意类型”,比如std::variant<:any ...></:any>,这等于放弃类型安全又没换来灵活性
替代 void* 和 union 的真实代价
它们确实消除了裸指针的悬垂风险和 union 的手动生命周期管理,但引入了新约束:类型必须明确、可比较、可拷贝(或移动)。尤其要注意 std::variant 中的类型不能是抽象类、数组或带删除拷贝构造的类型。
-
void*的自由是以放弃所有类型检查为代价的;std::any和std::variant把检查移到了更早阶段——一个在运行时,一个在编译时 - 兼容性上,
std::any要求 C++17,std::variant同样;老项目升级时注意 MSVC/GCC/Clang 版本是否支持完整特性(比如 Clang 5+ 才完全支持std::variant的 constexpr 构造) - 最容易被忽略的一点:
std::variant的std::monostate占位符不是摆设——它让 variant 可默认构造,否则所有类型都得有默认构造函数,这点在设计接口时经常卡住










