当前位置: 首页 > news >正文

【C++】设计一个单例基类,用户使用基类提供 getInstance() 来获取派生类实例化的单例对象

这是一个非常高级且实用的 C++ 设计模式需求。要实现一个基类,既能作为单例的通用接口,又能自动管理派生类的实例化,我们需要结合CRTP工厂模式

这种设计通常被称为“可注册的单例基类”

🎯 核心设计思路

  1. 基类 (SingletonBase)
    • 提供统一的getInstance()接口。
    • 维护一个工厂映射表(std::map),用于存储“类型ID”到“创建函数”的映射。
    • 提供registerType方法,允许派生类在程序启动时注册自己。
  2. 派生类 (Derived)
    • 继承自基类。
    • 在静态初始化阶段,自动向基类注册自己的“构造工厂”。
  3. 获取实例
    • 调用SingletonBase::getInstance<T>()时,基类查找映射表,动态创建并返回对应的派生类单例。

💻 代码实现

#include<iostream>#include<memory>#include<map>#include<string>#include<functional>#include<vector>// 1. 基类:提供统一的单例管理接口classSingletonBase{public:// 禁止拷贝SingletonBase(constSingletonBase&)=delete;SingletonBase&operator=(constSingletonBase&)=delete;// 虚析构函数,确保多态删除时安全virtual~SingletonBase()=default;// 业务接口virtualvoiddoWork()=0;// --- 核心魔法:泛型获取实例 ---template<typenameT>staticT&getInstance(){// 获取当前类型的唯一静态实例// C++11 保证 static 局部变量的线程安全初始化staticT instance;returninstance;}// --- 可选:通过字符串/ID 获取基类指针 (工厂模式) ---// 这在不知道具体类型,只知道“名字”时很有用usingCreatorFunc=std::function<SingletonBase*()>;staticstd::map<std::string,CreatorFunc>&getFactoryMap(){staticstd::map<std::string,CreatorFunc>factoryMap;returnfactoryMap;}// 注册类型到工厂staticvoidregisterType(conststd::string&name,CreatorFunc creator){getFactoryMap()[name]=creator;}// 通过名字创建对象 (注意:这里返回的是裸指针或智能指针,因为类型在运行时决定)staticSingletonBase*createByName(conststd::string&name){autoit=getFactoryMap().find(name);if(it!=getFactoryMap().end()){returnit->second();}returnnullptr;}protected:SingletonBase(){}};// ==========================================// 派生类 A// ==========================================classConcreteSingletonA:publicSingletonBase{public:// 2. 派生类必须禁止拷贝ConcreteSingletonA(constConcreteSingletonA&)=delete;ConcreteSingletonA&operator=(constConcreteSingletonA&)=delete;voiddoWork()override{std::cout<<"ConcreteSingletonA is working."<<std::endl;}voidspecificMethodA(){std::cout<<"Method specific to A."<<std::endl;}protected:// 3. 构造函数 protected,防止外部 new,只允许单例模式或基类管理ConcreteSingletonA(){std::cout<<"A constructed."<<std::endl;}~ConcreteSingletonA(){}private:// 4. 注册器类 (利用静态成员在程序启动时自动执行)structRegistrar{Registrar(){// 将 "TypeA" 这个名字映射到 创建 ConcreteSingletonA 的函数// 注意:这里我们注册的是创建单例的工厂,而不是直接 new 对象// 为了演示工厂模式,这里演示如何注册“创建单例”的逻辑registerType("TypeA",[](){// 这里返回的是单例的引用地址,但在工厂接口中我们通常返回指针// 为了保持单例特性,工厂内部也调用 getInstancereturn&getInstance<ConcreteSingletonA>();});std::cout<<"Registered TypeA."<<std::endl;}};// 静态成员变量,触发 Registrar 的构造staticRegistrar registrar;};// 初始化静态成员,触发注册ConcreteSingletonA::Registrar ConcreteSingletonA::registrar;// ==========================================// 派生类 B// ==========================================classConcreteSingletonB:publicSingletonBase{public:ConcreteSingletonB(constConcreteSingletonB&)=delete;ConcreteSingletonB&operator=(constConcreteSingletonB&)=delete;voiddoWork()override{std::cout<<"ConcreteSingletonB is working."<<std::endl;}protected:ConcreteSingletonB(){std::cout<<"B constructed."<<std::endl;}~ConcreteSingletonB(){}private:structRegistrar{Registrar(){registerType("TypeB",[](){return&getInstance<ConcreteSingletonB>();});std::cout<<"Registered TypeB."<<std::endl;}};staticRegistrar registrar;};ConcreteSingletonB::Registrar ConcreteSingletonB::registrar;// ==========================================// 使用演示// ==========================================intmain(){std::cout<<"--- 1. 直接使用泛型接口获取单例 (推荐) ---"<<std::endl;// 方式一:如果你知道具体类型,直接使用模板接口,最安全、最高效ConcreteSingletonA&a1=SingletonBase::getInstance<ConcreteSingletonA>();a1.doWork();ConcreteSingletonB&b1=SingletonBase::getInstance<ConcreteSingletonB>();b1.doWork();std::cout<<"\n--- 2. 验证单例唯一性 ---"<<std::endl;ConcreteSingletonA&a2=SingletonBase::getInstance<ConcreteSingletonA>();std::cout<<"a1 address: "<<&a1<<std::endl;std::cout<<"a2 address: "<<&a2<<std::endl;// 地址应该相同std::cout<<"\n--- 3. 使用工厂模式通过字符串获取 (多态) ---"<<std::endl;// 方式二:如果你只知道名字(比如配置文件读取),使用工厂模式std::string configType="TypeA";SingletonBase*ptr=SingletonBase::createByName(configType);if(ptr){ptr->doWork();// 多态调用}// 获取 TypeBSingletonBase*ptrB=SingletonBase::createByName("TypeB");if(ptrB){ptrB->doWork();// 验证工厂返回的也是单例 (地址应该和上面的 b1 相同)std::cout<<"Factory B address: "<<ptrB<<std::endl;std::cout<<"Direct B address: "<<&b1<<std::endl;}return0;}

🔑 关键点解析

1. 基类的template <typename T> static T& getInstance()

这是最核心的部分。

  • 我们将getInstance放在基类中,但是它是模板函数。
  • 当调用SingletonBase::getInstance<ConcreteSingletonA>()时,编译器会实例化这个函数,其中的static T instance实际上变成了static ConcreteSingletonA instance
  • 优点:你不需要在每个派生类里重复写getInstance代码。基类自动为每个类型T生成一个单例存储位置。
2. 自动注册机制 (Registrar)
  • 为了让基类知道有哪些派生类(为了支持createByName),我们需要在程序启动时注册它们。
  • 利用 C++ 全局/静态变量的初始化特性,我们在派生类内部定义一个struct Registrar和一个静态成员registrar
  • 当程序加载时,registrar被构造,它的构造函数会自动调用基类的registerType,将“类型名”和“创建该单例的 Lambda 函数”存入基类的 Map 中。
3. 线程安全
  • 代码中使用了Meyers’ Singleton(static T instance)。
  • C++11 标准保证静态局部变量的初始化是线程安全的(即“魔法静态变量”)。这意味着即使多线程同时调用getInstance,也只会创建一个实例。
4. 工厂模式与单例的结合
  • registerType中,我们注册的 Lambda 表达式是[]() { return &getInstance<T>(); }
  • 这非常巧妙:即使通过工厂接口(通常用于创建新对象)来获取实例,我们内部依然强制返回了单例对象的地址。这保证了无论通过哪种方式(模板直接调用 或 字符串工厂调用),拿到的都是同一个对象。

📌 总结

这种设计模式非常适合插件系统组件化架构

  • 扩展性强:新增一个单例类,只需继承SingletonBase并复制粘贴Registrar代码即可,无需修改基类。
  • 统一管理:所有单例的生命周期管理逻辑都集中在基类的模板方法中。
  • 多态支持:可以通过基类指针操作不同的单例实现。
http://www.jsqmd.com/news/617548/

相关文章:

  • 如何让OBS视频成为Windows应用的标准摄像头?OBS-VirtualCam深度解析
  • 3种颠覆性方式重新定义AI与浏览器的对话边界
  • 私有云部署实操:从零搭建企业内部云平台
  • 汽车电子EMC测试:RE试验与BCI整改实战解析
  • Qwen3.5-9B多卡并行教程:DeepSpeed Zero-3模型切分部署
  • Windows版Poppler终极安装指南:5分钟搞定PDF处理工具
  • Windows 11 24H2 LTSC 如何三步恢复微软商店?让精简系统重获完整应用生态的终极方案
  • StructBERT中文模型实战:GPU算力高效利用——单卡3090实测并发16路语义匹配
  • linux起源与哲学
  • 如何快速掌握PlugY:暗黑破坏神2单机玩家的终极生存指南
  • 温度参数调优:OpenClaw+Qwen3-4B不同任务下的creativity设置
  • 低成本AI部署新选择:Gemma-3-270m适配Jetson Nano边缘设备实测
  • 互联网大厂为啥不把研发迁到二三线城市?
  • OpenClaw白话讲解:AI如何从会聊天变成会干活
  • 桌面端 Claw 个人微信接入指南杆
  • RAG笔记——架构及检索方式
  • 查老板查企业:合法避坑指南+高效工具推荐
  • 深入解析 NVIDIA 显卡中 FP16 Tensor Core 与 FP16 算力的性能差异与应用场景
  • 手机卡就是SIM卡吗?真相揭秘
  • 揭秘!中国八大软件外包公司
  • Windows11 ARM系统直接运行X86 exe,高通CPU同时运行安卓APP,任意软件
  • 跨越代码的界限:深度解读 Cosmopolitan Writing Award (CWA) 国际写作比赛平台
  • 3分钟读懂汽车热管理核心技术与未来趋势
  • 2026年长沙热门的细胞存储公司排名,华启生物靠谱吗 - 工业品网
  • 终极跨平台资源嗅探工具:3步搞定微信视频号无水印下载
  • OpenClaw+SecGPT-14B组合应用:自动化红队工具箱搭建
  • OpenClaw权限管理实践:Phi-3-mini-128k-instruct访问敏感数据的防护策略
  • OFA视觉问答模型惊艳效果:‘Is there a tree’类存在性判断准确演示
  • 如何快速掌握网盘直链下载助手:新手必看的完整使用秘诀
  • 开发者应该掌握的思想谱系(七)PIMPL