varient与any
1. std::variant
(变体)
std::variant
是一个类型安全的联合体 (union)。它可以在编译时定义一个固定且有限的类型集合,并在运行时精确地持有其中一个类型的值。你总是知道 std::variant
可能包含哪些类型。
核心思想: “这个变量要么是一个 int
,要么是一个 double
,要么是一个 std::string
,但它在任何时候都只可能是其中一个。”
特点:
- 编译时已知类型集: 在声明
std::variant
时,你需要明确列出它可能包含的所有类型。 - 类型安全: 你不能意外地从
std::variant
中提取一个它当前不持有的类型的值。如果尝试这样做,会抛出std::bad_variant_access
异常或返回nullptr
。 - 空间效率: 通常会在栈上分配足够的空间来存储其所有可能类型中最大的那个,避免了动态内存分配(除非内部类型本身需要动态分配)。
- 无额外开销: 相比于多态(虚函数),
std::variant
通常没有虚函数表的开销。 - 访问方式:
std::get<T>(variant_obj)
:按类型获取值,如果类型不匹配则抛出std::bad_variant_access
。std::get<index>(variant_obj)
:按索引获取值,如果索引不匹配则抛出std::bad_variant_access
。std::get_if<T>(&variant_obj)
:按类型获取值的指针,如果类型不匹配则返回nullptr
。std::visit(callable, variant_obj...)
:这是最强大和推荐的访问方式。它接受一个或多个callable
对象(例如 lambda 表达式、函数对象)和一个或多个std::variant
对象,然后根据variant
当前持有的类型调用相应的callable
重载。这是一种类型安全的访问模式,类似于访问者模式。
- 空状态: 默认构造的
std::variant
会持有其第一个模板参数类型的默认构造值。如果你想表示一个“空”或“未初始化”的状态,可以使用std::monostate
作为它的第一个类型。
何时使用 std::variant
:
- 当你有一个有限且已知的类型集合,并且变量在任何时候只能是其中一个。
- 实现状态机。
- 函数返回多种可能的结果类型(例如
std::expected
的底层实现)。 - 解析抽象语法树 (AST) 节点。
- 替代类型不安全的 C 风格联合体。
示例:
1 |
|
2. std::any
(任意类型)
std::any
是一个类型安全的容器,它可以存储任何单个值,只要该类型是可复制构造 (CopyConstructible) 的。你不需要在编译时知道它可能包含哪些类型,它在运行时处理类型信息。
核心思想: “这个变量可以包含任何东西,但它在任何时候都只包含一个东西。我需要在使用时猜测并验证它里面是什么。”
特点:
- 运行时未知类型: 你不需要在声明
std::any
时指定它可能包含的类型。它可以在运行时存储任何可复制构造的类型。 - 类型擦除 (Type Erasure):
std::any
通过类型擦除技术来存储任意类型的数据。它内部维护了存储值的副本以及其类型信息。 - 动态内存分配: 通常会涉及动态内存分配来存储其内部的值,尤其当存储的对象大小超过某个阈值时(小对象优化可能会在栈上)。
- 运行时类型检查: 访问
std::any
中的值需要进行运行时类型检查。 - 访问方式:
std::any_cast<T>(any_obj)
:尝试将any_obj
中的值转换为类型T
。如果any_obj
为空或存储的类型不是T
,则抛出std::bad_any_cast
异常。std::any_cast<T>(&any_obj)
:尝试将any_obj
中的值转换为类型T
的指针。如果any_obj
为空或存储的类型不是T
,则返回nullptr
。any_obj.has_value()
:检查any_obj
是否包含值。any_obj.type()
:返回std::type_info
对象,提供关于存储类型的信息(通常用于调试或更复杂的类型检查)。
何时使用 std::any
:
- 当你需要存储真正任意的类型,并且在编译时无法预知所有可能的类型。
- 实现插件系统,插件可以返回任意类型的数据。
- 配置系统,配置项的值可以是各种类型。
- 事件系统,事件携带的数据可以是任意类型。
- 需要传递任意上下文数据到通用函数中。
示例:
1 |
|
std::variant
vs std::any
总结对比
特性 | std::variant |
std::any |
---|---|---|
类型集合 | 编译时已知,固定,有限 | 运行时未知,任意 (任何 CopyConstructible 类型) |
安全性 | 编译时安全 (通过 std::visit 和类型列表) |
运行时安全 (通过 std::any_cast 检查) |
性能/开销 | 通常更高,无动态分配(除非内部类型大),无类型擦除开销 | 通常较低,涉及动态分配和类型擦除开销 |
内存分配 | 通常在栈上,大小为最大成员类型的大小 | 通常在堆上(对于大对象),涉及动态内存管理 |
访问方式 | std::get , std::get_if , std::visit (推荐) |
std::any_cast (可能抛异常或返回 nullptr ) |
适用场景 | 有限的、已知的、固定的几种类型选择 | 任意的、未知的类型,需要极致的灵活性 |
空状态 | 默认持有第一个类型的值,可使用 std::monostate |
has_value() 为 false 时为空,或调用 reset() 清空 |
简单来说:
- 如果你知道所有可能的类型,并且这个集合是固定的,使用
std::variant
。它提供了更好的编译时安全性和性能。 - 如果你需要存储任何类型的数据,并且在编译时无法预知所有可能性,使用
std::any
。它提供了最大的灵活性,但代价是运行时开销和更多的运行时类型检查。
All articles on this blog are licensed under CC BY-NC-SA 4.0 unless otherwise stated.
Comments