<p>不能直接用 void* 做泛型存储,因为类型擦除后丢失析构信息和拷贝语义,delete 会崩溃,memcpy 复制非 POD 类型(如 std::string)导致双释放;std::any 通过“类型擦除 + 函数指针表”安全解决,核心是为每种类型存构造、析构、拷贝、移动、类型查询等操作函数,而非仅存数据。</p>

为什么不能直接用 void* 做泛型存储
因为类型擦除后丢失了析构信息和拷贝语义,delete 时会崩溃,memcpy 复制非 POD 类型(比如 std::string)会双释放。C++17 的 std::any 能安全处理这些,核心在于“类型擦除 + 函数指针表”,自己实现就得模拟这套机制。
如何设计最小可行的 Any 类结构
关键不是存数据,而是存“怎么管它”——得为每种类型生成一套操作函数:构造、析构、拷贝、移动、类型查询。用 void* 存原始字节,用函数指针数组(const void* 指向类型 ID,其他函数指针指向操作逻辑)做调度。
-
Any内部持有一个void*和一个const void*(指向类型信息),再加一个函数指针表(如struct Op { void (*dtor)(void*); void (*copy)(void*, const void*); ... }*) - 模板构造函数负责根据传入类型
T生成专属操作函数,并把对象就地构造到内部缓冲区(或堆上) - 拷贝/移动赋值必须调用对应类型的
copy/move函数,不能直接memcpy - 类型查询靠比较
typeid(T).name()或更稳妥的typeid(T).hash_code(),但注意跨编译单元可能不一致,建议用std::type_info&引用作键
Any::has_value() 和 Any::type() 怎么判断空状态
不能只看 data_ == nullptr,因为有些类型(如 int)可能被就地构造在小缓冲区里,data_ 指向栈内地址。正确做法是引入独立标志位或用 op_ == nullptr 表示空态。
- 初始化时把
op_设为nullptr,所有操作函数先检查if (!op_) return; -
type()返回std::type_info const&,空时抛std::bad_any_cast或返回typeid(void)(需约定) - 别依赖
data_是否为空指针判断是否有值——它可能指向内部 32 字节缓冲区
如何安全取出值:Any::cast<t>()</t> 的实现要点
运行时类型检查失败时必须抛异常,不能静默失败。标准做法是用 typeid(T) == stored_type_info,但要注意 typeid 对 cv 限定符敏感,const int 和 int 是不同类型。
立即学习“C++免费学习笔记(深入)”;
- 推荐用
std::type_info::hash_code()比较,比==更快且规避字符串比较开销 - 取出时先检查类型匹配,再通过
static_cast<t>(*data_)</t>(就地构造)或*static_cast<t>(data_) </t>(堆分配)访问 - 如果用户写了
any.cast<int>()</int>,而内部存的是int值而非引用,应拒绝——Any不支持存储引用类型(标准std::any也不支持) - 避免模板参数推导陷阱:不要写
cast<t>()</t>,而要强制用户显式写cast<int>()</int>,否则T可能被推成int&导致后续static_cast错误
真正麻烦的是小对象优化(SOO)与堆分配的切换逻辑、对齐处理、以及移动后原 Any 必须置空——这些细节一旦漏掉,就会在 std::vector<any></any> 里悄悄崩溃。


















