std::any¶
| 标准 | 难度 | 状态 | 分级 | 证据 | 审阅 | 最后校验 |
|---|---|---|---|---|---|---|
| C++17 | 2/5 | final | A | 3 条(manual 3) | 单审(zyuhao7) | 2026-10-25 |
编译器支持:GCC 7+ · Clang 4+ · MSVC 19.11+。版本查询:cppreference 编译器支持。
主张与证据¶
| 主张 | 支持证据 |
|---|---|
| 用 std::any 作为请求级键值容器的值类型 | ev-drogon-any-attributes-001 |
| 会话容器按需创建 any 值,并在锁内做类型检查后调用处理器 | ev-drogon-any-session-modify-003 |
| 读取前比较 typeid,再用 any_cast 指针形式取地址 | ev-drogon-any-view-data-typecheck-002 |
本站证据页:ev-drogon-any-attributes-001 · ev-drogon-any-session-modify-003 · ev-drogon-any-view-data-typecheck-002
解决什么问题¶
请求属性、会话数据、插件参数这类容器,需要在同一个键值表里存放不同类型的值。用 void * 可以放任意指针,但类型信息、所有权和析构责任全部丢失;用基类指针加虚函数,则要求所有值共享继承体系,普通 int、std::string 无法直接放入。
std::any 保存一个任意可复制类型的值,同时记录类型信息,并在销毁、复制、移动时调用正确的操作。它适合类型集合在编译期无法枚举的场景。代价是读取方必须知道具体类型,并显式用 std::any_cast 取回。
基础用法¶
#include <any>
#include <iostream>
#include <string>
int main() {
std::any value = 42;
if (value.type() == typeid(int))
std::cout << std::any_cast<int>(value) << '\n';
value = std::string("atlas");
std::cout << std::any_cast<const std::string &>(value) << '\n';
}
完整示例见 examples/cpp17/any.cpp:
count: int 42
name: string atlas
item: user amy 3
item: int 7
item: string x
after reset: empty
amy
pointer cast 7
bad_any_cast
示例把 int、std::string 与自定义结构体放进同一个 std::vector<std::any>,用 type() 分派、用引用版 any_cast 读取大对象,并演示指针版转换与类型不符时的异常。
进阶用法¶
指针版 any_cast 不抛异常。 std::any_cast<T>(&value) 返回 T *,类型不匹配或值为空时返回 nullptr。值版与引用版在失败时抛 std::bad_any_cast。需要探测时用指针版,已经确认类型时再用值版或引用版。
只做精确类型匹配。 存入 int 后不能用 any_cast<long> 读取,即使 int 可以隐式转成 long。同理,std::any value = "hello"; 存的是 const char *,用 any_cast<std::string> 读取会失败。需要字符串就显式构造 std::string。
值版会复制,引用版不会。 any_cast<std::string>(value) 返回副本;any_cast<const std::string &>(value) 返回引用。大对象优先用引用版,并保证 any 在引用使用期间存活。
只能存放可复制类型。 std::any 的复制构造要求内部值可复制,因此 std::unique_ptr<int> 不能直接放入。需要共享所有权时存 std::shared_ptr<int>;需要独占所有权,改用自定义的类型擦除容器或 std::variant 加指针。
空状态是合法状态。 默认构造的 std::any 与调用 reset() 之后的 std::any 都不含值;has_value() 为假,任何值版/引用版 any_cast 都抛 bad_any_cast。读取前先检查。
emplace<T> 就地替换。 value.emplace<Widget>(args...) 直接构造新值,避免先造临时对象再复制;返回新值的引用。
实现可能分配堆内存。 标准不要求小对象优化。小整数、指针可能就地保存,较大的对象通常需要堆分配;高频路径不要用 std::any 代替静态类型。
type() 与 RTTI。 std::any::type() 在多数实现中依赖 RTTI;例如 libstdc++ 在 -fno-rtti 下不提供 type(),但 any_cast 仍可通过内部管理器函数指针比较工作。这不是跨实现保证;需要在禁用 RTTI 的环境存放异构值时,改用显式的标签联合或自定义类型擦除。
真实项目证据¶
| 项目 | 位置 | 用法 | 为什么典型 |
|---|---|---|---|
| drogonframework/drogon | lib/inc/drogon/Attribute.h#L61-L75 |
请求属性表使用 map<string, any>,普通值可隐式存入 |
框架与业务代码共享同一容器,类型在编译期不可枚举 |
| drogonframework/drogon | lib/inc/drogon/HttpViewData.h#L40-L53 |
读取前比较 typeid,再用指针版 any_cast 取地址 |
精确类型匹配;类型不符时记录错误并回退到默认值 |
| drogonframework/drogon | lib/inc/drogon/Session.h#L108-L122 |
会话容器在互斥量保护下查找、校验类型并传出引用 | 跨请求异构状态;缺失时默认构造后插入 |
三条证据均为 manual 级:作者复核,尚未引入第二名审核者。drogon 的 CMake 配置要求 C++17,三个容器都把 std::any 用作公开 API 的值类型。
陷阱与边界¶
any_cast不做转换。 只匹配存储的精确类型;int与long、const char *与std::string都不会互相兼容。- 值版转换会复制。 对
std::vector、大结构体反复调用值版any_cast会产生意外拷贝。 - 空
any转换会抛异常。 默认构造或reset()之后,先用has_value()判断,或改用指针版转换。 - 不能存移动-only 类型。
unique_ptr、std::mutex等不可复制类型无法放入;这是std::any的类型要求,不是实现限制。 - 没有统一访问接口。
std::any不提供visit、比较或哈希;每个使用点都要自己枚举已知类型,类型集合一旦扩大,分派代码要同步修改。 - 指针值不拥有对象。 存入
T *只是保存地址;对象生命周期仍由调用方管理,容易产生悬垂。 - 异常路径要处理。 值版转换与引用版转换都可能抛
bad_any_cast,公共 API 边界应捕获或先做类型检查。 - 性能不可忽略。 类型擦除、可能的堆分配与 RTTI 都比静态类型慢;热路径优先用模板或
std::variant。
库联动¶
- drogon:请求属性、视图数据与会话状态都用
std::any作为异构值容器;读取端通过typeid与any_cast恢复类型。 - Abseil:新版本把
absl::any定义为std::any的别名,并废弃旧的 polyfill;使用 Abseil 的项目可以直接采用标准类型。 - nlohmann/json:隐式转换的约束显式排除了
std::any,避免把类型擦除值误当成 JSON 可序列化类型;需要序列化时先any_cast到具体类型。 - fmt / spdlog:标准库没有为
std::any提供 formatter;日志输出前应先按类型分派,或把值转成字符串再记录。 - 与
std::variant的分工:类型集合封闭、需要穷尽检查时用variant;类型集合开放、由插件或业务方在运行期决定时用any。
横向对比¶
| 方案 | 类型安全 | 类型集合 | 所有权 | 主要代价 |
|---|---|---|---|---|
std::any |
运行期检查 | 开放 | 拥有可复制值 | 可能堆分配;读取需知道类型 |
void * |
否 | 开放 | 不拥有 | 类型与析构全部手工管理 |
std::variant<Ts...> |
编译期检查 | 封闭 | 拥有值 | 新增类型需重编译并更新访问代码 |
| 基类 + 虚函数 | 是 | 开放(继承) | 通常堆上 | 要求共同基类;间接调用 |
std::shared_ptr<void> |
否 | 开放 | 共享 | 丢失类型信息;引用计数开销 |
absl::any |
运行期检查 | 开放 | 拥有可复制值 | 新版本已退化为 std::any 别名 |
跨标准演进:std::any 随 P0220R1 进入 C++17;此后接口保持稳定,没有加入访问器或比较操作。需要模式匹配或穷尽分派时,应改用 std::variant,或在 any 之上维护显式的标签与分派表。