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

C++ 单例模式(Singleton)详解

单例模式(Singleton)是一种 设计模式,用于保证某个类只有一个实例,并提供全局访问点。它 不是 C++ 的语言特性,只是通过 C++ 提供的语法和特性实现的一种约定。

在 C++ 中,你甚至不一定需要类:函数和变量可以独立存在,因此单例更多是 组织全局状态的一种方式,类似命名空间。使用类的好处是可以:

  • 封装状态和逻辑。

  • 提供访问控制。

  • 支持成员变量和成员函数。


为什么要用单例?

  • 全局唯一:保证整个程序只有一个实例。

    • 配置管理器(ConfigManager)

    • 日志系统(Logger)

    • 如果使用普通类,每个模块都可能创建自己的实例,这样就无法保证全局状态一致。
      单例模式就是解决这个问题的一种方式。

    • 随机数生成器(Random)

  • 支持类特性:可以包含成员变量、函数和访问控制。

  • 延迟初始化:资源只有在第一次访问时分配,避免浪费。

  • 可扩展性:可以随时增加方法或状态管理,而不破坏结构。


单例模式的核心思想

单例模式主要依赖 三点

  1. 私有构造函数:防止外部随意创建对象。

  2. 静态实例:类内保存一个静态对象,确保唯一性。

  3. 静态访问方法:提供全局访问点,返回静态对象引用。

例子:

class Singleton
{
public:// 静态访问该类 GetInstance() 或者简写为 Get() 单例类只有一个实例 所以返回那个实例的引用static Singleton& GetInstance() // 这是一个静态方法 它就是Singleton::GetInstance() 只能调用静态变量 但是s_Instance就是静态变量{return s_Instance;}void Function() {}private:Singleton() {}; // Singleton不能有public的构造函数 否则就会允许被实例化 此处意味着该类不能再外部被实例化static Singleton s_Instance; // 在private 只创建一次单例类的静态实例
};// 静态成员变量必须在类外定义
Singleton Singleton::s_Instance;int main()
{// 通过GetInstance()来访问这个单例 Singleton::GetInstance()就是那个单例Singleton& instance = Singleton::GetInstance(); // 一定要用引用 而不是复制// 假如这个实例想调用什么函数Singleton::GetInstance().Function();instance.Function(); // 和上面那句的含义是一样的
}
  • GetInstance() 是静态方法,返回 唯一实例 的引用。

  • 构造函数是私有的,防止外部创建多个对象。

  • 静态成员 s_Instance 在类外初始化,只创建一次。

注意:C++ 并不会阻止用户拷贝实例,如果不加限制,多次拷贝会破坏单例约束。


防止拷贝破坏单例

在 C++ 中,类如果没有显式声明拷贝构造函数和赋值运算符,编译器会默认生成它们。

对于单例模式来说:

Random copy = Random::GetInstance(); // 会调用拷贝构造函数

如果允许拷贝,就会产生 新的实例,破坏单例 “全局唯一”的初衷。

解决方法:在 public 里显式删除拷贝构造和赋值运算符:

class Singleton {
public:Singleton(const Singleton&) = delete;            // 删除拷贝构造Singleton& operator=(const Singleton&) = delete; // 删除赋值运算符static Singleton& GetInstance() { return s_Instance; }private:Singleton() {}static Singleton s_Instance;
};

这样尝试复制单例对象会直接 编译错误,保证唯一性。


局部静态实例

传统实现需要在类外初始化静态成员,有翻译单元依赖问题。现代 C++ 可以使用 局部静态变量 延迟初始化:

// 随机数生成器
class Random
{
public:Random(const Random&) = delete;// 禁止拷贝static Random& GetInstance(){return s_Instance;}float Float() { return m_RandomGenerator; }private:Random() {}; // 私有构造函数float m_RandomGenerator = 0.5f; // 就假装这个是我们用某种方式生成的随机数static Random s_Instance; // 静态实例
};Random Random::s_Instance;// 传统静态成员初始化int main()
{float number = Random::GetInstance().Float(); // 这样就生成了一个随机数
}
  • instance 只会创建一次,生命周期长,线程安全(C++11 起)。

  • 使用 Random::Float() 就能获取随机数,无需每次都显式调用 GetInstance()

使用单例类 就是因为它实际上是一个类 可以支持所有类特性 比如类成员变量

静态方法封装内部实现

为了更方便访问,我们可以把内部成员函数封装起来,通过 静态方法调用:

// 随机数生成器
class Random
{
public:Random(const Random&) = delete;static Random& GetInstance(){return s_Instance;}static float Float() { return GetInstance().IFloat(); } // 静态方法
private:float IFloat() { return m_RandomGenerator; } // 也可以用FloatImpl Impl是implementation 但是IFloat看起来更像一个接口 意思就是Internal内部的Float函数Random() {};float m_RandomGenerator = 0.5f;static Random s_Instance;
};Random Random::s_Instance;int main()
{float number = Random::Float(); // 就不需要再使用Random::GetInstance().Float()
}

好处:

  • 调用方式更简洁:Random::Float()

  • 不需要每次都写 Random::GetInstance().Float()

  • 对外只暴露接口,隐藏内部实现

局部 static 替代类静态成员

传统静态成员:

  • 必须在类外初始化:Random Random::s_Instance;

  • 对整个类可见

  • 存在翻译单元依赖问题(如果有多个 cpp 文件,初始化顺序需要注意)

现代 C++ 推荐 局部静态变量

class Random {
public:Random(const Random&) = delete;static Random& GetInstance() {static Random instance; // 局部 static,只在第一次调用时创建return instance;}static float Float() { return GetInstance().IFloat(); }private:float IFloat() { return m_RandomGenerator; }Random() {};float m_RandomGenerator = 0.5f;
};

解释局部 static 的意义

  1. 只在方法第一次调用时初始化

    • 延迟初始化(Lazy Initialization),节省启动开销。

    • 对象的生命周期从第一次访问开始,到程序结束。

  2. 作用域仅限方法内部

    • 外部无法直接访问 instance,保证封装性。

  3. 线程安全(C++11 以后)

    • 局部 static 的初始化在多线程环境下是安全的,不需要额外锁。

  4. 无需在类外初始化

    • 解决了传统静态成员需要在 cpp 文件外部定义的问题。


单例 vs 命名空间

方式优点缺点
命名空间简单、无需实例化无法管理状态生命周期
单例类封装、状态管理、访问控制需要注意拷贝和初始化

总结:单例类就是一种 组织全局对象的方式,既能保证唯一性,又能利用类特性。


单例模式的注意事项

  1. 避免拷贝:显式删除拷贝构造函数和赋值操作,确保全局唯一。

  2. 生命周期管理:局部静态变量生命周期到程序结束,传统静态成员也类似,但需要类外定义。

  3. 线程安全:局部静态变量初始化自 C++11 起是线程安全的。

  4. 使用场景:仅在需要全局唯一对象时使用单例,避免滥用全局状态。

总结

单例模式的本质是 类 + 静态实例 + 静态访问方法,并通过:

  • 私有构造函数避免外部实例化。

  • 删除拷贝构造与赋值运算符,防止复制。

  • 使用局部静态实例,实现延迟初始化和线程安全。

虽然完全可以用命名空间实现类似功能,但使用单例类能够更好地组织全局状态,并支持类的特性和面向对象扩展。

http://www.dtcms.com/a/461457.html

相关文章:

  • 面向未来的数据平台
  • C++5d
  • Transformer实战(21)——文本表示(Text Representation)
  • 网站空间商 权限梵克雅宝
  • 【Vue 3 】——setup、ref、watch
  • 做期货网站违法的吗淄博市住房和城乡建设局网站
  • 使用feign进行远程调用出现的问题(文件服务参数接收为null)
  • 国自然·医工交叉热点|通用医学影像分割基础模型与数据库
  • React Native:关于react自定义css属性的位置
  • 对于el-table中自定义表头中添加el-popover会弹出两个的解决方案,分别针对固定列和非固定列来隐藏最后一个浮框。
  • 电子商务公司简介系统清理优化工具
  • 内网渗透实战:红队作战全解析
  • Verilog和FPGA的自学笔记4——多路选择器1(always语句)
  • 前端架构师,是架构什么
  • Coze源码分析-资源库-编辑数据库-后端源码-安全与错误处理
  • 制作专业网站餐厅网络推广方案
  • 掌握MyBatis Java API:高效操作数据库
  • 搭建网站 程序招工网站怎么做
  • 数据库设计_理论部分_设计方法设计过程
  • 【三维重建-算法解析】MVS(Multi-View Stereo,多视图立体)
  • 【GPT5系列】ChatGPT5 提示词工程指南
  • 61850协议GOOSE通信AB网通信
  • wordpress开启子站找公司做网站有什么好处
  • SpringBoot+Redis实现电商秒杀方案
  • 电子商务网站模板 html数据型网站
  • 【QT常用技术讲解】QSerialPort串口开发,包含文件发送功能
  • STM32 外设驱动模块【含代码】:SG90 舵机模块
  • 深圳城乡和住房建设局网站263企业邮箱官网登录
  • K8s概念基础(一)
  • 计算机视觉毕业设计选题该如何选?——根据自身情况合理选择