在 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
}

 关键点:

  1. std::any 是一个可以容纳任意类型的容器。

  2. 赋值时,它会复制对象(或者移动对象),内部会保存一份完整拷贝。

  3. 使用 std::any_cast<T> 取出时,必须保证类型完全匹配,否则抛出 std::bad_any_cast 异常。

  4. 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::coutconst 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::variantstd::any 对比

std::variantstd::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 提供了极大的灵活性,但这也带来了类型不透明的问题。
通常在以下情况中,不建议使用:

  1. 类型可枚举的情况(推荐用 std::variant

  2. 性能敏感场景(避免堆分配和 RTTI)

  3. 类型信息必须保持一致的模块接口(推荐模板或多态)

举个例子:

// 低效写法
std::any config = 3.14;
config = "pi";

//  更安全的选择
std::variant<int, double, std::string> config;

八、总结

std::any 的设计目标是让 C++ 拥有类似动态语言的灵活性
但它并不是为通用场景准备的万能解法。

结论:

  • 当你确实无法预知类型时(如反射、通用容器、脚本接口),使用 std::any

  • 当类型可预测或有限时,优先选择 std::variant,性能更高、类型更安全。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐