不能直接用 void* 存任意类型,因其仅保存地址而无类型信息与析构逻辑,导致读取错误和资源泄漏;需通过虚函数表实现完整类型擦除,封装构造、拷贝、移动、析构等操作契约。

为什么不能直接用 void* 存任意类型
因为 void* 只保存地址,不带类型信息和析构逻辑。你存一个 std::string 进去,取出来时若只按 int* 解引用,不仅读错内存,对象也不会被析构——资源泄漏是必然的。
真正要复刻 std::any 的核心,是把「类型擦除」做完整:既要藏住类型,又要能在需要时安全构造、拷贝、移动、析构。
常见错误是只管存储不管销毁,比如:
void* ptr = new std::string("hello");
// 后续没 delete static_cast<std::string*>(ptr) → 泄漏
如何设计一个最小可行的 type-erased storage
关键不是指针本身,而是用函数指针或虚函数封装操作契约。推荐用虚函数表(vtable)方式,更清晰可控。
立即学习“C++免费学习笔记(深入)”;
- 定义一个基类
any_storage_base,含纯虚函数:clone()、move_to()、destroy()、type_info() - 模板派生类
any_storage_impl<t></t>实现这些接口,内部持有一个T成员(非指针,避免额外分配) -
my_any类只持有一个any_storage_base*,并在构造/赋值时 new 对应的 impl
这样所有类型行为被收敛到虚函数里,my_any 本身完全无模板参数,真正实现类型擦除。
拷贝和移动时容易踩的坑
拷贝构造必须调用 clone(),而不是浅拷贝指针;移动赋值后原对象的 storage_ptr 必须置为 nullptr,否则析构两次会 crash。
典型错误写法:
my_any& operator=(const my_any& other) {
if (this != &other) {
delete storage_ptr;
storage_ptr = other.storage_ptr; // ❌ 浅拷贝 → double-delete
}
return *this;
}
正确做法是:
- 让
other.storage_ptr->clone()返回新堆对象 - 确保
clone()内部 new 出的是对应any_storage_impl<t></t>,而非基类 - 移动构造中,把
other.storage_ptr指针移交并设为nullptr
如何安全取出原始类型(type-safe access)
不能靠强制转型,必须运行时校验类型。标准做法是每个 any_storage_impl<t></t> 在 type_info() 中返回 typeid(T),取出时用 typeid(U) == stored_type_info() 判断。
示例接口:
template<typename U>
U& cast() & {
if (type_info() != typeid(U)) throw std::bad_any_cast{};
return static_cast<any_storage_impl<U>*>(storage_ptr)->value;
}
注意:cast<u>()</u> 返回引用,意味着底层对象生命周期必须长于引用使用期;如果存的是临时对象(如 my_any{42}),取出来的 int& 是悬垂引用——这点和 std::any 一致,但容易被忽略。
真正难处理的是 cv 限定符和引用折叠:比如存 const std::string&,取出时需匹配 const std::string&,不能只认 std::string。实际项目中建议先支持值语义,再按需扩展。


















