news 2026/6/2 19:37:57

设计模式入门:1. 单例模式详解 C++实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
设计模式入门:1. 单例模式详解 C++实现

设计模式入门:1. 单例模式详解 C++实现

前言:什么是设计模式?

在软件开发的世界里,我们经常会遇到一些重复出现的问题。设计模式(Design Pattern)就是这些问题的经过验证的、通用的解决方案。它们不是具体的代码,而是一套解决特定问题的最佳实践和思想

设计模式最早由"四人帮"(GoF,Gang of Four)在1994年的《设计模式:可复用面向对象软件的基础》一书中系统地提出,共包含23种经典模式,分为三大类:

  • 创建型模式:关注对象的创建过程,如单例、工厂、建造者等
  • 结构型模式:关注类和对象的组合结构,如适配器、装饰器、代理等
  • 行为型模式:关注对象间的交互和职责分配,如观察者、策略、迭代器等

学习设计模式的好处不言而喻:

  • 提高代码的可复用性和可维护性
  • 提供了一套通用的"设计语言",让开发者之间的沟通更高效
  • 帮助我们写出更优雅、更健壮的代码

今天,我们就从最简单也最常用的单例模式开始我们的设计模式之旅。


单例模式概述

什么是单例模式?

单例模式(Singleton Pattern)是一种创建型设计模式,它的核心思想非常简单:确保一个类只有一个实例,并提供一个全局访问点来获取这个实例

换句话说,单例模式保证了在整个程序生命周期内,某个类只能被实例化一次,所有地方访问到的都是同一个对象。

单例模式的应用场景

单例模式适用于以下场景:

  • 资源管理器:如日志管理器、配置管理器、数据库连接池
  • 全局状态管理:如游戏中的游戏管理器、应用程序的主控制器
  • 硬件访问:如打印机驱动、传感器控制器
  • 工具类:如数学工具类、字符串工具类(不过现代C++更推荐使用命名空间)

单例模式的优缺点

优点:

  • 严格控制对实例的访问
  • 避免频繁创建和销毁对象,提高性能
  • 节省系统资源
  • 提供全局统一的访问点

缺点:

  • 违反了单一职责原则(一个类既要负责自己的业务逻辑,又要负责实例的创建)
  • 扩展困难,因为单例类没有抽象层
  • 对测试不友好,难以模拟单例对象
  • 在多线程环境下需要特别注意线程安全问题

C++实现单例模式的几种方式

在C++中,实现单例模式有多种方式,每种方式都有其优缺点和适用场景。我们将从最简单的版本开始,逐步演进到最推荐的版本。

1. 饿汉式单例(Eager Initialization)

饿汉式单例是最简单的实现方式,它在程序启动时就创建实例,不管你用不用。

// SingletonEager.hclassSingletonEager{public:// 禁止拷贝和移动SingletonEager(constSingletonEager&)=delete;SingletonEager&operator=(constSingletonEager&)=delete;SingletonEager(SingletonEager&&)=delete;SingletonEager&operator=(SingletonEager&&)=delete;// 全局访问点staticSingletonEager&getInstance(){returninstance;}// 示例业务方法voiddoSomething(){// 业务逻辑}private:// 私有构造函数,防止外部实例化SingletonEager(){// 初始化代码}// 静态成员变量,程序启动时就创建staticSingletonEager instance;};// SingletonEager.cpp// 在类外初始化静态成员变量SingletonEager SingletonEager::instance;

优点:

  • 实现简单
  • 天生线程安全(因为静态变量在程序启动时就初始化了)

缺点:

  • 程序启动时就创建实例,即使从未使用过,也会占用内存
  • 如果有多个单例类,且它们之间有依赖关系,可能会出现初始化顺序问题

2. 懒汉式单例(Lazy Initialization)- 基础版

懒汉式单例是延迟初始化的,只有在第一次调用getInstance()时才创建实例。

// SingletonLazyBasic.hclassSingletonLazyBasic{public:// 禁止拷贝和移动SingletonLazyBasic(constSingletonLazyBasic&)=delete;SingletonLazyBasic&operator=(constSingletonLazyBasic&)=delete;SingletonLazyBasic(SingletonLazyBasic&&)=delete;SingletonLazyBasic&operator=(SingletonLazyBasic&&)=delete;// 全局访问点staticSingletonLazyBasic*getInstance(){if(instance==nullptr){instance=newSingletonLazyBasic();}returninstance;}// 示例业务方法voiddoSomething(){// 业务逻辑}private:// 私有构造函数SingletonLazyBasic(){// 初始化代码}// 静态指针,初始化为nullptrstaticSingletonLazyBasic*instance;};// SingletonLazyBasic.cppSingletonLazyBasic*SingletonLazyBasic::instance=nullptr;

优点:

  • 延迟初始化,只有在需要时才创建实例,节省内存
  • 避免了饿汉式的初始化顺序问题

缺点:

  • 线程不安全!在多线程环境下,如果多个线程同时进入if (instance == nullptr)判断,可能会创建多个实例

3. 线程安全的懒汉式单例 - 双重检查锁(DCL)

为了解决基础版懒汉式的线程安全问题,我们可以使用双重检查锁(Double-Checked Locking)。

// SingletonDCL.h#include<mutex>classSingletonDCL{public:// 禁止拷贝和移动SingletonDCL(constSingletonDCL&)=delete;SingletonDCL&operator=(constSingletonDCL&)=delete;SingletonDCL(SingletonDCL&&)=delete;SingletonDCL&operator=(SingletonDCL&&)=delete;// 全局访问点staticSingletonDCL*getInstance(){// 第一次检查:如果实例已经存在,直接返回,避免每次都加锁if(instance==nullptr){// 加锁,保证只有一个线程进入下面的代码块std::lock_guard<std::mutex>lock(mutex);// 第二次检查:防止多个线程同时通过第一次检查后,重复创建实例if(instance==nullptr){instance=newSingletonDCL();}}returninstance;}// 示例业务方法voiddoSomething(){// 业务逻辑}private:// 私有构造函数SingletonDCL(){// 初始化代码}// 静态指针和互斥量staticSingletonDCL*instance;staticstd::mutex mutex;};// SingletonDCL.cppSingletonDCL*SingletonDCL::instance=nullptr;std::mutex SingletonDCL::mutex;

优点:

  • 线程安全
  • 延迟初始化
  • 性能较好,只有第一次创建实例时才需要加锁

注意:
在C++11之前,由于编译器的指令重排问题,双重检查锁可能会失效。但在C++11及以后的标准中,new操作符的语义得到了明确,双重检查锁是安全的。

4. 局部静态变量单例(Meyers’ Singleton)- 最推荐的方式

这是由Scott Meyers提出的一种非常优雅的单例实现方式,也是现代C++中最推荐的单例实现方式

// SingletonMeyers.hclassSingletonMeyers{public:// 禁止拷贝和移动SingletonMeyers(constSingletonMeyers&)=delete;SingletonMeyers&operator=(constSingletonMeyers&)=delete;SingletonMeyers(SingletonMeyers&&)=delete;SingletonMeyers&operator=(SingletonMeyers&&)=delete;// 全局访问点staticSingletonMeyers&getInstance(){// 局部静态变量,第一次调用时初始化staticSingletonMeyers instance;returninstance;}// 示例业务方法voiddoSomething(){// 业务逻辑}private:// 私有构造函数SingletonMeyers(){// 初始化代码}};

为什么这是最推荐的方式?

  • 实现最简单:代码量最少,最容易理解和维护
  • 线程安全:在C++11及以后的标准中,局部静态变量的初始化是线程安全的
  • 延迟初始化:只有在第一次调用getInstance()时才创建实例
  • 没有内存泄漏问题:静态变量在程序结束时会自动销毁

5. 模板单例

如果我们需要多个单例类,为每个类都写一遍单例代码会很繁琐。这时我们可以使用模板来实现一个通用的单例基类。

// SingletonTemplate.htemplate<typenameT>classSingletonTemplate{public:// 禁止拷贝和移动SingletonTemplate(constSingletonTemplate&)=delete;SingletonTemplate&operator=(constSingletonTemplate&)=delete;SingletonTemplate(SingletonTemplate&&)=delete;SingletonTemplate&operator=(SingletonTemplate&&)=delete;// 全局访问点staticT&getInstance(){staticT instance;returninstance;}protected:// 保护构造函数,允许子类继承SingletonTemplate()=default;virtual~SingletonTemplate()=default;};// 使用示例classMyClass:publicSingletonTemplate<MyClass>{// 让基类可以访问私有构造函数friendclassSingletonTemplate<MyClass>;public:voiddoSomething(){// 业务逻辑}private:MyClass(){// 初始化代码}};// 调用方式// MyClass::getInstance().doSomething();

优点:

  • 代码复用,避免重复编写单例逻辑
  • 所有单例类的实现方式统一

缺点:

  • 子类需要将基类声明为友元,稍微有点麻烦

单例模式的注意事项和常见陷阱

1. 内存泄漏问题

在使用指针实现的懒汉式单例中,new出来的对象在程序结束时不会自动销毁,可能会导致内存泄漏。虽然现代操作系统会在程序结束时回收所有内存,但这仍然是一个不好的编程习惯。

解决方法:

  • 使用Meyers’ Singleton(局部静态变量),它会自动销毁
  • 使用智能指针(std::unique_ptr)来管理实例
  • 提供一个destroyInstance()方法,在程序结束时手动调用

2. 多线程安全问题

这是单例模式中最容易出错的地方。在多线程环境下,一定要确保单例的创建是线程安全的。

永远不要使用基础版的懒汉式单例,除非你能保证它只会在单线程环境下使用。

3. 序列化和反序列化问题

如果单例类需要支持序列化和反序列化,那么反序列化时可能会创建新的实例,破坏单例的特性。

解决方法:

  • 重写反序列化方法,让它返回已有的实例
  • 避免让单例类支持序列化

4. 反射问题

在支持反射的语言(如Java、C#)中,可以通过反射调用私有构造函数来创建新的实例。不过C++没有原生的反射机制,所以这个问题在C++中不常见。


总结

单例模式是最简单也最常用的设计模式之一,它确保一个类只有一个实例,并提供全局访问点。

在C++中,**Meyers’ Singleton(局部静态变量)**是最推荐的实现方式,它简单、线程安全、延迟初始化且没有内存泄漏问题。

实现方式线程安全延迟初始化实现复杂度推荐指数
饿汉式⭐⭐⭐
基础懒汉式
双重检查锁⭐⭐⭐⭐
Meyers’ Singleton极低⭐⭐⭐⭐⭐
模板单例⭐⭐⭐⭐

最后需要提醒的是,单例模式虽然好用,但不要滥用。只有当你确实需要确保一个类只有一个实例时,才应该使用单例模式。过度使用单例会导致代码耦合度增加,难以测试和维护。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/2 19:35:02

海康威视CVE-2017-7921漏洞复现与深度分析

文章前言 现如今,家用、商用网络摄像头已经遍布大街小巷、私人庭院与企业园区,海量设备直接暴露在公网之中。而海康威视作为全球安防摄像头龙头厂商,其设备的安全漏洞一旦被利用,将直接造成隐私泄露、监控画面被窃取、设备被完全接管等严重安全事件。 本文将结合实机测…

作者头像 李华
网站建设 2026/6/2 19:35:01

[Dify实战] 如果要让后端系统批量调用 Dify,接口、回传和状态跟踪应该怎么起步?

账号定位:技术小甜甜(new-main) 今日目标:发布今日第一篇主推 CSDN 文章 专栏/系列:AI实践-Dify专栏 很多团队把 Dify 接进项目的第一步,都走得很快: 先在界面里把应用跑通; 拿一把 API Key 做一次调用; 在 Postman 里看见返回内容; 然后就准备让后端系统批量接进来。…

作者头像 李华
网站建设 2026/6/2 19:29:29

深度解析F3D三维查看器的模块化渲染架构与高性能实现原理

深度解析F3D三维查看器的模块化渲染架构与高性能实现原理 【免费下载链接】f3d Fast and minimalist 3D viewer. 项目地址: https://gitcode.com/GitHub_Trending/f3/f3d F3D是一款基于VTK渲染引擎构建的轻量级三维查看器&#xff0c;采用模块化插件架构设计&#xff0c…

作者头像 李华
网站建设 2026/6/2 19:29:28

PingFangSC字体包:跨平台字体一致性解决方案技术指南

PingFangSC字体包&#xff1a;跨平台字体一致性解决方案技术指南 【免费下载链接】PingFangSC PingFangSC字体包文件、苹果平方字体文件&#xff0c;包含ttf和woff2格式 项目地址: https://gitcode.com/gh_mirrors/pi/PingFangSC PingFangSC字体包为开发者和设计师提供了…

作者头像 李华
网站建设 2026/6/2 19:29:04

3步解决电路图绘制难题:如何用Draw.io创建教科书级工程图纸

3步解决电路图绘制难题&#xff1a;如何用Draw.io创建教科书级工程图纸 【免费下载链接】Draw-io-ECE Custom-made draw.io-shapes - in the form of an importable library - for drawing circuits and conceptual drawings in draw.io. 项目地址: https://gitcode.com/gh_m…

作者头像 李华