C++17 新特性:std::any —— 存放任意类型的容器
在 C++17 中,std::any 提供了一种通用的类型安全容器,可以在单个变量中存储任意类型的数据。
相比 void* 或 union,它的优势是:类型安全、支持任意复杂类型(含非平凡构造/析构),同时提供了安全的类型恢复机制。
一、基础用法
#include <any>
#include <iostream>
int main() {
std::any data;
data = 39;
data = "Miku";
data = std::string("Miku");
std::cout << std::any_cast<std::string>(data) << std::endl; // 输出 Miku
}
关键点:
-
std::any是一个可以容纳任意类型的容器。 -
赋值时,它会复制对象(或者移动对象),内部会保存一份完整拷贝。
-
使用
std::any_cast<T>取出时,必须保证类型完全匹配,否则抛出std::bad_any_cast异常。 -
std::any每次只能存 一个类型的一个对象。
如果你往里面赋了新类型,旧类型就被销毁。
取出时,any_cast的模板参数 必须和当前存储的类型一模一样,
否则会抛出std::bad_any_cast异常。
二、字符串存储行为详解
字符串是 std::any 最容易混淆的地方:
std::any data;
data = "Miku"; // 实际类型:const char*
此时 data 内部存储的是一个 const char*,并不是 std::string。
区别在于:
-
"Miku"是一个字符串字面量,类型是const char[5](包含结尾\0)。 -
数组在赋值时退化为
const char*。 -
所以
std::any实际保存的是const char*。
三、类型安全与隐式转换
std::variant 会在赋值时进行隐式类型匹配:
std::variant<int, std::string> v;
v = "Miku"; // 自动转换成 std::string
但 std::any 不会自动转换:
std::any data;
data = "Miku"; // 存的是 const char*
std::string s = std::any_cast<const char*>(data); // 手动取出
如果直接写成:
std::string s = std::any_cast<std::string>(data);
会抛出异常:
std::bad_any_cast: bad any_cast
因为内部类型不匹配。
四、std::cout 的行为小陷阱
std::any data = "Miku";
std::cout << std::any_cast<const char*>(data) << std::endl;
输出结果是:
Miku
不是指针地址。
这是因为 std::cout 对 const char* 有特殊处理:它会将其视为 C 风格字符串,自动解引用打印内容。
若要输出真正的指针地址:
std::cout << static_cast<const void*>(std::any_cast<const char*>(data)) << std::endl;
五、内部机制与性能
std::any 内部有一个类型擦除(type-erasure)机制:
每次存储一个对象时,会在堆上或栈上保存其拷贝,并记录对应的 typeid 信息。
其底层大致包含以下部分:
-
一个指向类型信息的指针(
std::type_info) -
一个存储区域(可以放小对象)
-
一个用于动态分配大对象的指针
小对象优化(Small Object Optimization, SOO)
对于小于 32 字节(实现相关)的类型,std::any 会直接在内部存储,不触发 new。
超过这个大小时,它会在堆上动态分配对象。
例如:
std::any a = 42; // 栈上存储(小对象)
std::any b = std::string(1000, 'x'); // 堆上分配
相比之下,std::variant 的空间是编译期确定的,不会动态分配,因此性能更高。
六、std::variant 与 std::any 对比
std::variant 和 std::any 看起来都能“装下任意类型的数据”,
但它们的设计初衷、使用场景和底层机制完全不同。
std::variant —— 有规则的盒子
“只能放这几种类型中的一个,超出范围编译器直接报错。”
std::any —— 无规则的盒子
“你随便放什么都行,我不会管,但你取的时候要自己记得是什么。”
| 特性 | std::variant |
std::any |
|---|---|---|
| 类型安全 | 编译期检查 | 运行期检查 |
| 存储对象 | 限定类型集合 | 任意类型 |
| 性能 | 高(固定空间) | 较低(可能动态分配) |
| 类型恢复 | std::get / std::get_if |
std::any_cast |
| 动态扩展性 | 不支持 | 任意类型 |
| 使用场景 | 明确类型集合 | 插件系统、通用事件总线等动态系统 |
简单理解:
-
variant更像一个「安全版 union」。 -
any更像一个「类型安全的 void*」。
七、何时不该用 std::any
std::any 提供了极大的灵活性,但这也带来了类型不透明的问题。
通常在以下情况中,不建议使用:
-
类型可枚举的情况(推荐用
std::variant) -
性能敏感场景(避免堆分配和 RTTI)
-
类型信息必须保持一致的模块接口(推荐模板或多态)
举个例子:
// 低效写法
std::any config = 3.14;
config = "pi";
// 更安全的选择
std::variant<int, double, std::string> config;
八、总结
std::any 的设计目标是让 C++ 拥有类似动态语言的灵活性,
但它并不是为通用场景准备的万能解法。
结论:
当你确实无法预知类型时(如反射、通用容器、脚本接口),使用
std::any。当类型可预测或有限时,优先选择
std::variant,性能更高、类型更安全。
更多推荐
所有评论(0)