如何使用std::shared_ptr与std::any实现带类型安全检查的非模板数据容器?
这是个非常实用的需求——用std::shared_ptr<void>做类型擦除确实能实现存储任意类型,但类型检查的缺失很容易导致运行时错误。结合C++17的特性,我们可以完美解决这个问题,同时完全满足你的所有要求。
方案一:手动维护类型信息(轻量可控)
核心思路是在容器里额外存储一份数据的类型标识,在get()时对比目标类型和存储类型是否一致,不匹配则抛出异常。这里用std::type_index来存储类型信息,开销极小:
#include <memory> #include <typeindex> #include <stdexcept> class Container { std::shared_ptr<void> data_; std::type_index type_id_; // 禁用拷贝/移动,符合你的需求 Container(const Container&) = delete; Container& operator=(const Container&) = delete; Container(Container&&) = delete; Container& operator=(Container&&) = delete; public: Container() = default; // 对外暴露的set接口:直接接收任意shared_ptr<T>,自动记录类型 template<typename T> void set(const std::shared_ptr<T>& ptr) { data_ = ptr; type_id_ = std::type_index(typeid(T)); } // 对外暴露的get接口:指定目标类型,类型不匹配则抛异常 template<typename T> std::shared_ptr<T> get() { if (type_id_ != std::type_index(typeid(T))) { throw std::bad_cast(); // 也可以返回nullptr,根据需求调整 } return std::static_pointer_cast<T>(data_); } // 可选:添加类型检查方法,避免不必要的异常 template<typename T> bool holds_type() const { return type_id_ == std::type_index(typeid(T)); } };
这个方案的优势是极致轻量,只比原来的std::shared_ptr<void>方案多存储一个std::type_index(本质是整数),完全没有额外的性能开销,同时保证了类型安全。
方案二:用std::any包装std::shared_ptr(简洁易用)
C++17的std::any本身就是为类型安全的类型擦除设计的,我们可以直接用它来存储std::shared_ptr<T>,让std::any帮我们处理类型检查:
#include <memory> #include <any> #include <stdexcept> class Container { std::any data_; // 禁用拷贝/移动 Container(const Container&) = delete; Container& operator=(const Container&) = delete; Container(Container&&) = delete; Container& operator=(Container&&) = delete; public: Container() = default; template<typename T> void set(const std::shared_ptr<T>& ptr) { data_ = ptr; // 直接将shared_ptr<T>存入std::any } template<typename T> std::shared_ptr<T> get() { try { return std::any_cast<std::shared_ptr<T>>(data_); } catch (const std::bad_any_cast& e) { // 可以重新抛出自定义异常,或者返回nullptr throw std::bad_cast(); } } template<typename T> bool holds_type() const { return data_.type() == typeid(std::shared_ptr<T>); } };
关于你补充的疑问:用std::any存std::shared_ptr是不是良好实践?
完全是良好实践,原因如下:
- 标准库原生支持:
std::any是C++17标准引入的类型安全类型擦除工具,经过充分测试,比手动维护类型信息更可靠,不容易出错。 - 无数据拷贝:我们存储的是
std::shared_ptr<T>的副本,而不是T本身,所以完全不会拷贝你说的“大体积数据”,只是复制了一个智能指针(开销可以忽略)。 - 代码简洁:不需要手动处理类型对比和存储,
std::any_cast会自动完成类型检查,代码可读性和可维护性更好。
方案对比与选择
- 如果你对性能有极致要求(比如嵌入式场景),可以选择手动维护类型信息的方案,它的开销几乎可以忽略。
- 大多数常规场景下,
std::any方案更推荐,因为代码更简洁、更符合现代C++风格,且标准库已经帮我们处理了所有类型安全的细节。
两种方案都严格遵循了你的API设计要求:set()接收任意std::shared_ptr<T>,get<U>()返回std::shared_ptr<U>且类型不匹配时会触发安全检查。
内容的提问来源于stack exchange,提问作者Yuchen Wu
相关产品推荐
相关产品推荐

