C++模板问题:如何根据类型选择对应容器
可行解决方案
方法1:基于std::type_index + std::any的异构容器
利用C++20/23的std::type_index作为键,std::any存储不同类型的ResourceMap容器,上层用std::unordered_map统一管理。通过模板函数封装容器的获取逻辑,彻底避免逐个类型判断的冗余代码。
核心实现代码
#include <unordered_map> #include <typeindex> #include <any> #include <memory> #include <stdexcept> // 定义单资源类型的容器模板 template<typename T> using ResourceMap = std::unordered_map<std::string, std::shared_ptr<T>>; class ResourceManager { private: std::unordered_map<std::type_index, std::any> m_resource_containers; // 模板函数:获取对应类型的ResourceMap,不存在则自动创建 template<typename T> ResourceMap<T>& get_resource_map() { const auto type_idx = std::type_index(typeid(T)); auto it = m_resource_containers.find(type_idx); if (it == m_resource_containers.end()) { auto [new_it, success] = m_resource_containers.emplace( type_idx, ResourceMap<T>() ); return std::any_cast<ResourceMap<T>&>(new_it->second); } return std::any_cast<ResourceMap<T>&>(it->second); } public: // 加载资源:模板参数指定资源类型 template<typename T> std::shared_ptr<T> Load(const std::string& path) { auto& map = get_resource_map<T>(); // 检查是否已加载 if (auto it = map.find(path); it != map.end()) { return it->second; } // 实际加载逻辑(示例) auto resource = std::make_shared<T>(); // resource->LoadFromFile(path); // 替换为真实加载代码 map[path] = resource; return resource; } // 检查资源是否已加载 template<typename T> bool IsResourceLoaded(const std::string& path) { try { auto& map = get_resource_map<T>(); return map.contains(path); } catch (const std::bad_any_cast&) { // 容器不存在,说明从未加载过该类型资源 return false; } } };
优势
- 新增资源类型无需修改
ResourceManager核心逻辑,直接使用模板方法即可,扩展性极强 - 代码简洁,完全依赖标准库特性实现异构容器管理
方法2:CRTP+静态注册表(零运行时开销)
通过CRTP(奇异递归模板模式)让每个资源类型静态注册自身的容器,上层用std::tuple存储所有类型的容器引用,利用C++23的std::tuple_find在编译时定位对应容器,完全消除运行时类型转换开销。
核心实现代码
#include <tuple> #include <unordered_map> #include <memory> #include <string> // 前置声明ResourceManager class ResourceManager; // CRTP基类,用于静态注册资源容器 template<typename Derived> class ResourceRegistrar { protected: friend class ResourceManager; static inline ResourceMap<Derived> s_resource_map; }; // 资源示例类型:继承ResourceRegistrar即可完成注册 class Texture : public ResourceRegistrar<Texture> { // Texture的具体实现 }; class Model : public ResourceRegistrar<Model> { // Model的具体实现 }; // 单资源类型容器模板 template<typename T> using ResourceMap = std::unordered_map<std::string, std::shared_ptr<T>>; class ResourceManager { private: // 存储所有注册资源容器的引用元组(C++23支持可变模板推导) using ContainerTuple = std::tuple< ResourceMap<Texture>&, ResourceMap<Model>& >; ContainerTuple m_containers{ Texture::s_resource_map, Model::s_resource_map }; // 编译时获取对应类型容器的元组索引 template<typename T> ResourceMap<T>& get_resource_map() { constexpr auto idx = std::tuple_find<ContainerTuple, ResourceMap<T>&>(); return std::get<idx>(m_containers); } public: template<typename T> std::shared_ptr<T> Load(const std::string& path) { auto& map = get_resource_map<T>(); if (auto it = map.find(path); it != map.end()) { return it->second; } auto resource = std::make_shared<T>(); // resource->LoadFromFile(path); // 替换为真实加载代码 map[path] = resource; return resource; } template<typename T> bool IsResourceLoaded(const std::string& path) { auto& map = get_resource_map<T>(); return map.contains(path); } };
优势
- 完全编译时推导,无运行时类型转换或哈希查找开销
- 新增资源类型只需继承
ResourceRegistrar并更新ContainerTuple,逻辑清晰可控
方法3:利用C++23的std::variant + 类型访问器
如果资源类型数量固定且有限,可使用std::variant存储所有ResourceMap,配合std::visit和模板访问器实现类型安全的容器操作,避免std::any的运行时转换异常风险。
核心实现代码
#include <variant> #include <unordered_map> #include <memory> #include <string> // 单资源类型容器模板 template<typename T> using ResourceMap = std::unordered_map<std::string, std::shared_ptr<T>>; // 资源示例类型 class Texture {}; class Model {}; class Audio {}; // 定义包含所有资源容器的variant类型 using ResourceContainer = std::variant< ResourceMap<Texture>, ResourceMap<Model>, ResourceMap<Audio> >; class ResourceManager { private: std::unordered_map<std::type_index, ResourceContainer> m_containers; template<typename T> ResourceMap<T>& get_resource_map() { const auto type_idx = std::type_index(typeid(T)); auto& var = m_containers[type_idx]; // 确保variant存储的是对应类型的容器 if (!std::holds_alternative<ResourceMap<T>>(var)) { var = ResourceMap<T>(); } return std::get<ResourceMap<T>>(var); } public: template<typename T> std::shared_ptr<T> Load(const std::string& path) { auto& map = get_resource_map<T>(); if (auto it = map.find(path); it != map.end()) { return it->second; } auto resource = std::make_shared<T>(); // resource->LoadFromFile(path); // 替换为真实加载代码 map[path] = resource; return resource; } template<typename T> bool IsResourceLoaded(const std::string& path) { const auto type_idx = std::type_index(typeid(T)); auto it = m_containers.find(type_idx); if (it == m_containers.end()) return false; return std::visit([&path](auto& map) { using MapType = decltype(map); if constexpr (std::is_same_v<MapType, ResourceMap<T>>) { return map.contains(path); } else { return false; } }, it->second); } };
优势
- 利用
std::variant的类型安全特性,避免std::any的运行时转换异常 - 适合资源类型数量明确、变动较少的场景
内容的提问来源于stack exchange,提问作者Aleix Rius
相关产品推荐
相关产品推荐

