C++如何实现可控制单例生命周期的SingletonFactory单例工厂
解决方案
问题根因说明
- 第一个版本
delete void*报错的核心原因是C++标准不允许对void*指针执行delete操作:编译器既不知道对应内存的大小,也不知道要调用哪个类型的析构函数,且无法通过运行时的type_index信息反推编译期类型完成转换,该思路不可行。 - 第二个版本无法清理缓存的核心原因是你将不同模板参数对应的缓存声明为了函数静态变量,这些缓存属于独立的模板实例,不归属于任何
SingletonFactory对象,工厂实例无法遍历到所有分散的静态缓存,自然无法统一清理。
可行实现方案
我们可以通过类型擦除+虚析构的思路,把不同类型、不同参数的缓存的清理逻辑统一封装,全部挂载到工厂实例的成员变量中,即可实现生命周期与工厂完全绑定,按需销毁所有单例。
完整实现代码如下:
#include <map> #include <tuple> #include <typeindex> #include <unordered_map> #include <utility> class SingletonFactory { public: // 所有缓存持有者的抽象基类,定义统一的析构接口 struct IHolder { virtual ~IHolder() = default; }; // 对应每种<类型, 参数组合>的缓存持有者,负责自身缓存的清理 template<typename T, typename... Args> struct TupleHolder : IHolder { using ArgsTuple = std::tuple<std::decay_t<Args>...>; std::map<ArgsTuple, const T*> cache; ~TupleHolder() override { for (auto& [_, obj_ptr] : cache) { delete obj_ptr; } } }; template<typename T, typename... Args> const T& get(Args&&... args) { // 生成当前<类型, 参数组合>对应的唯一type_index const auto holder_type = std::type_index(typeid(TupleHolder<T, Args...>)); // 查找对应缓存持有者,不存在则创建 if (!_holders.contains(holder_type)) { _holders.emplace(holder_type, new TupleHolder<T, Args...>()); } auto* holder = static_cast<TupleHolder<T, Args...>*>(_holders.at(holder_type)); // 查找对应参数的实例,不存在则创建 auto args_tuple = std::make_tuple(std::forward<Args>(args)...); auto it = holder->cache.find(args_tuple); if (it != holder->cache.end()) { return *it->second; } auto* new_obj = new T(std::forward<Args>(args)...); holder->cache.emplace(std::move(args_tuple), new_obj); return *new_obj; } // 主动清理所有单例的接口,也可以等工厂析构自动清理 void clear() { _holders.clear(); } ~SingletonFactory() = default; private: std::unordered_map<std::type_index, IHolder*> _holders; };
使用说明
- 你可以将
SingletonFactory声明为栈对象,比如在main函数开头创建,那么main函数结束时工厂自动析构,所有单例都会被按顺序释放,不会进入程序终止阶段的析构,完全避免析构顺序混乱的问题。
示例用法:
int main() { SingletonFactory factory; const auto& rect1 = factory.get<FixedOutlineRect2D>(1, 2); const auto& rect2 = factory.get<FixedOutlineRect2D>(3, 4); // 业务逻辑 // 不需要手动清理,main结束时factory析构自动释放所有rect实例 return 0; }
- 如果需要全局唯一的工厂,也可以给工厂本身加Meyer单例接口,如果需要提前销毁所有单例,主动调用
clear()方法即可。
方案优势
- 所有单例生命周期完全和工厂绑定,销毁时机完全可控
- 支持同类型不同参数构造不同单例,兼容你第二个版本的需求
- 虚析构自动处理资源释放,不需要手动处理类型转换,没有内存泄漏风险
- 不需要维护分散的静态缓存,所有资源都由工厂实例统一管理
内容的提问来源于stack exchange,提问作者mpen
相关产品推荐
相关产品推荐

