如何在C++工厂中按类型过滤对象?探讨无实例化实现方案
问题描述
我现有一个AppFactory工厂类,内部通过map存储对象名称与创建函数的映射,核心代码如下:
template<class T> static T* makeObj() { return new T(); } using createFunction = std::function<void*()>; map<std::string, createFunction> types; template<class T> static void add(const std::string& name) { types.insert(name, makeObj<T>); }
现在我要添加一个模板函数getFilteredListOfTypes<BaseType>,用来获取所有继承自BaseType的类名称列表,函数原型如下:
template<class baseType> static std::list<std::string> getFilteredListOfTypes() { }
我现在卡在实现这个函数的思路上:
- 第一种思路:修改map的存储值为包含创建函数和对应类型实例的结构体,通过
dynamic_cast判断类型关系,但担心实例化大量大型对象会带来性能开销; - 第二种思路:使用
std::is_base_of模板判断继承关系,但该模板需要明确基类和派生类类型,我能从函数参数获取基类类型,但无法获取已注册的派生类类型,要是通过创建实例再用decltype获取类型,又回到了实例化的问题。
我想知道:
- 是否存在无需创建对象即可实现该过滤函数的方案?
- 当前基于实例化的实现方案是否存在弊端?
- 当工厂中注册数百个大型类型时,实例化每个类型是否会引发问题?
现有完整实现代码
AppFactory.h
class AppFactory { public: template<class T> static T* makeObj() { return new T(); } using createFunction = std::function<void*()>; using registerMap = tsl::robin_map<std::string, createFunction>; static registerMap& get(); template<class T> static std::unique_ptr<T> createUnique(const std::string& name) { return std::unique_ptr<T>(create<T>(name)); } template<class T> static std::shared_ptr<T> createShared(const std::string& name) { return std::shared_ptr<T>(create<T>(name)); } template<class T> static T* create(const std::string& name) { if(get().contains(name)) { return static_cast<T*>(get()[name]()); } return nullptr; } template<class T> static bool add(const std::string& name) { auto resultPair = get().insert_or_assign(name, makeObj<T>); return resultPair.second; } }; #define APPFACTORY_ADD(classname) \ namespace { static bool addName = AppFactory::add<classname>(#classname); }
AppFactory.cpp
AppFactory::registerMap& AppFactory::get() { static AppFactory::registerMap map; return map; }
工厂使用示例
... APPFACTORY_ADD(SomeClass); ... AppFactory::createUnique<SomeBaseClass>("SomeClass") ...
基于实例化的
getFilteredListOfTypes测试实现 struct typedata { createFunction func; Object* obj; }; using registerMap = tsl::robin_map<std::string, typedata>; ... template<class T> static bool add(const std::string& name) { typedata data; data.func = makeObj<T>; data.obj = new T(); auto resultPair = get().insert_or_assign(name, data); return resultPair.second; } ... template<class BaseType> static std::list<std::string> getFilteredListOfTypes() { std::list<std::string> typeList; for(auto it = get().begin(); it != get().end(); ++it) { if(dynamic_cast<BaseType*>(get()[it->first].obj)) { typeList.push_back(it->first); } } return typeList; }
过滤函数使用示例
class A : public Object ... class B : public A ... class C : public B ... class D : public C ... class E : public B ... lst = AppFactory::getFilteredListOfTypes<A>(); // lst -> {"A", "B", "C", "D", "E"} lst = AppFactory::getFilteredListOfTypes<B>(); // lst -> {"B", "C", "D", "E"} lst = AppFactory::getFilteredListOfTypes<C>(); // lst -> {"C", "D"} lst = AppFactory::getFilteredListOfTypes<D>(); // lst -> {"D"}
解答
1. 无需实例化对象的实现方案
有两种可行的无实例化方案,前提是你的基类/派生类是多态类型(包含虚函数,比如虚析构函数):
方案一:利用空指针dynamic_cast特性
C++标准允许对空指针执行dynamic_cast:如果派生类可以安全转换为目标基类,转换会返回非空的空指针;否则返回nullptr。我们可以基于这个特性修改工厂的注册逻辑:
// 修改存储条目,增加获取派生类空指针的函数 struct TypeEntry { createFunction creator; void* (*getDerivedNullPtr)(); }; using registerMap = tsl::robin_map<std::string, TypeEntry>; // 辅助函数:返回指定类型的空指针 template<class T> static void* getNullPtrForType() { return static_cast<void*>(static_cast<T*>(nullptr)); } // 修改注册函数 template<class T> static bool add(const std::string& name) { TypeEntry entry; entry.creator = makeObj<T>; entry.getDerivedNullPtr = &getNullPtrForType<T>; return get().insert_or_assign(name, entry).second; } // 过滤函数实现 template<class BaseType> static std::list<std::string> getFilteredListOfTypes() { static_assert(std::is_polymorphic_v<BaseType>, "BaseType must be polymorphic (have virtual functions)"); std::list<std::string> result; for (const auto& [name, entry] : get()) { void* derivedNull = entry.getDerivedNullPtr(); // 尝试将派生类空指针转换为基类指针 if (dynamic_cast<BaseType*>(derivedNull) != nullptr) { result.push_back(name); } } return result; }
这个方案完全不需要实例化对象,仅通过指针类型转换完成继承关系判断,性能开销极小。
方案二:编译期类型收集(仅适用于静态注册场景)
如果所有类型都是通过APPFACTORY_ADD宏在编译期注册的,可以利用元编程技术将所有注册类型收集到一个编译期列表中,再通过std::is_base_of_v筛选:
// 定义类型列表元编程结构 template<typename... Ts> struct TypeList {}; // 全局类型列表(需要修改注册宏来填充) inline auto& globalTypeList() { static TypeList<> list; return list; } // 修改注册宏,将类型添加到全局列表 #define APPFACTORY_ADD(classname) \ namespace { \ static bool addName = AppFactory::add<classname>(#classname); \ static auto registerType = [](){ \ globalTypeList() = TypeList<classname, decltype(globalTypeList())::types...>(); \ return true; \ }(); \ } // 过滤函数通过遍历编译期类型列表实现 template<class BaseType, typename... Ts> static std::list<std::string> filterTypes(TypeList<Ts...>) { std::list<std::string> result; ((std::is_base_of_v<BaseType, Ts> ? result.push_back(#Ts) : void()), ...); return result; } template<class BaseType> static std::list<std::string> getFilteredListOfTypes() { return filterTypes<BaseType>(globalTypeList()); }
这种方案完全在编译期完成筛选,运行时无开销,但仅适用于静态注册的类型,无法支持动态加载的插件类型。
2. 基于实例化的实现方案的弊端
- 内存浪费:所有注册类型的实例会长期驻留内存,大型对象会快速消耗内存资源。
- 初始化副作用:对象构造函数可能执行复杂逻辑(如分配资源、连接外部服务),导致程序启动缓慢,甚至引发不必要的副作用。
- 析构风险:如果实例未被正确析构会导致内存泄漏;若析构函数有副作用,可能影响程序稳定性。
- 线程安全隐患:多线程环境下注册实例时,若构造函数未做同步,可能引发竞态条件。
3. 数百个大型类型实例化的问题
当注册数百个大型类型时,实例化会带来严重问题:
- 内存耗尽:单个大型对象可能占用几十MB内存,数百个实例会直接耗尽可用内存,导致程序崩溃或卡顿。
- 启动延迟:每个对象的初始化逻辑叠加后,程序启动时间会显著延长,严重影响用户体验。
- 资源竞争:若多个对象构造函数访问共享资源(如数据库、文件),会引发资源竞争,进一步降低性能。
- 维护成本上升:单个类型的构造函数变更可能影响整个工厂的注册流程,增加维护难度。
内容的提问来源于stack exchange,提问作者Andrii
相关产品推荐
相关产品推荐

