设计模式——单例模式

作者:舒克起飞了日期:2025/10/2

单例模式是一种创建型设计模式,它可以确保一个类在整个程序运行过程中只有一个实例,并提供一个全局访问点以获取该实例。

单例模式的核心思想就是:控制对象的实例化,防止创建多个实例,从而节省资源并保证行为一致性。

关键点:

  • 单例类:包含单例实例的类,通常将构造函数声明为私有;
  • 静态成员变量:用于存储单例实例的静态成员变量;
  • 获取实例方法:静态方法,用于获取单例实例;
  • 私有构造函数:防止外部直接实例化单例类;
  • 线程安全处理:确保在多线程环境下单例实例的创建是安全的。
  • 构造函数和析构函数是私有的,不允许外部生成和释放
  • 静态成员变量和静态返回单例的成员函数
  • 禁用拷贝构造和赋值运算符

单例类主要是通过一个公共的静态方法getinstance接口,用于获取该类的实例,如果实例不存在,则在该方法内部创建实例并返回。

也就是说,单例类的构造方法不让其他人进行修改和使用;并且单例类只创建一个实例,这个实例,其他人也无法修改和直接使用;然后单例类提供一个调用方法,想用这个实例,就只能进行调用,确保了全局只创建一个实例。

基本写法:

下面是一个最基础的实现:

1class Singleton
2{
3private:
4    Singleton() {}        // 私有构造函数
5    ~Singleton() {}       // 私有析构函数
6    Singleton(const Singleton&) = delete;                // 禁用拷贝构造
7    Singleton& operator = (const Singleton&) = delete;   // 禁用赋值操作符
8
9public:
10    static Singleton* getInstance() {
11        static Singleton instance;
12        return &instance;
13    }
14
15    void show() {
16        cout << "Singleton Show" << endl;
17};
18
19// 静态成员初始化
20Singleton* Singleton::instance = nullptr;
21
22int main() {
23    Singleton::getInstance()->show();
24
25    return 0;
26}
27    

其实他有两种方式懒汉模式和饿汉模式

懒汉模式

其核心是延迟初始化,可以理解为它很懒,所以它一直没有初始化,只有在首次调用getInstance()时才创建单例实例。

  • 优点:节省资源,单例对象未被使用,则不会创建。
  • 缺点:需要考虑线程安全的问题,多线程下可能会重复创建。

饿汉模式

其核心是提前初始化,即在程序启动时直接创建单例实例,无论是否被使用。

  • 优点:线程安全,实例在程序启动时初始化,避免多线程竞争。
  • 缺点:浪费资源,即使没有使用单例对象,也会占用内存。

实际开发中建议使用C++11的局部静态变量懒汉模式

实现过程分析

下面是针对懒汉模式的一个实现过程的分析

示例1

1class Singleton1 {
2public:
3    // 要点2
4	static Singleton1 * GetInstance() {
5		if(_instance == nullptr) {
6			_instance = new Singleton1();
7		}
8		return _instance;
9	}
10private:
11    // 要点1
12	Singleton1() {}
13	~Singleton1() {
14		std::cout << "~Singleton1()\n";
15	}
16    // 要点3
17	Singleton1(const Singleton1 &) = delete;
18	Singleton1& operator = (const Singleton1&) = delete;
19	Singleton1(Singleton1 &&) = delete;
20	Singleton1& operator = (Singleton1 &&) = delete;
21	// 要点2
22	static Singleton1 *_instance; 
23};
24Singleton1* Singleton1::_instance = nullptr;
25

错误的点:_instance = new Singleton1();堆上的资源不能得到正确的析构,资源还是可以释放,但是在释放的时候是无法调用析构函数的。

示例2

1class Singleton2 {
2public:
3	static Singleton2 * GetInstance() {
4		if(_instance == nullpte) {
5			_instance = new Singleton2();
6			atexit(Destructor);
7		}
8		return _instance;
9	}
10private:
11	static void Destructor() {
12		if(nullptr != _instance) {
13			delete _instance;
14			_instance = nullptr;
15		}
16	}
17	Singleton2() {}
18	~Singleton2() {
19		std::cout << "~Singleton2()\n";
20	}
21	Singleton2(const Singleton2 &) = delete;
22	Singleton2& operator = (const Singleton2&) = delete;
23	Singleton2(Singleton2 &&) = delete;
24	Singleton2& operator = (Singleton2 &&) = delete;
25	
26    static Singleton2 *_instance; 
27};
28Singleton2* Singleton2::_instance = nullptr;
29

相对于示例1,添加了一个atexit()方法,这个方法就是程序退出的时候,它会去调用Destructor函数,就可以在这个函数里面实现一个手动的析构

示例3

1class Singleton3 {
2public:
3	static Singleton3 * GetInstance() {
4		std::lock_guard<std::mutex> lock(_mutex);
5		if(_instance == nullptr) {
6			std::lock_guard<std::mutex> lock(_mutex);
7			if(_instance == nullptr) {
8				_instance = new Singleton3();
9				// 1. 分配内存
10				// 2. 调用构造函数
11				// 3. 返回对象指针 
12				atexit(Destructor);
13			}
14		}
15		return _instance;
16	}
17private:
18	static void Destructor() {
19		if(nullptr != _instance) {
20			delete _instance;
21			_instance = nullptr;
22		}
23	}
24	Singleton3() {}
25	~Singleton3() {
26		std::cout << "~Singleton3()\n";
27	}
28	Singleton3(const Singleton3 &) = delete;
29	Singleton3& operator = (const Singleton3&) = delete;
30	Singleton3(Singleton3 &&) = delete;
31	Singleton3& operator = (Singleton3 &&) = delete;
32	
33	static Singleton3 *_instance; 
34	static std::mutex _mutex;
35};
36Singleton3* Singleton3::_instance = nullptr;
37std::mutex Singleton3::_mutex;
38

要实现一个线程安全的单例模式,如果只用if外面那个锁,它是单检测的情况下,它总是返回那个_instance是线程安全的,缺点是,new Singleton3()只会有一次,其他情况都是拿到instance然后进行返回。两个if中间加锁是为了防止有两个线程同时进入而导致new了两次。但是也不对,没有考虑到多线程情况下,指令重排的问题。

示例4

1class Singleton4 {
2public:
3	static Singleton4 * GetInstance() {
4		Singleton4* tmp = _instance.load(std::memory_order_relaxed);
5		std::atomic_thread_fence(std::memory_order_acquire);
6		if(tmp == nullptr) {
7			std::lock_guard<std::mutex> lock(_mutex);
8			tmp = _instance.load(std::memory_order_relaxed);
9			if(tmp == nullptr) {
10				tmp = new Singleton4();
11				std::atomic_thread_fence(std::memory_order_release);
12				_instance.store(tmp, std::memory_order_relaxed);
13				atexit(Destructor);
14			}
15		}
16		return tmp;
17	}
18private:
19	static void Destructor() {
20		Singleton4* tmp = _instance.load(std::memory_order_relaxed);
21		if(nullptr != tmp) {
22			delete tmp;
23		}
24	}
25	Singleton4() {}
26	~Singleton4() {
27		std::cout << "~Singleton4()\n";
28	}
29	Singleton4(const Singleton4 &) = delete;
30	Singleton4& operator = (const Singleton4&) = delete;
31	Singleton4(Singleton4 &&) = delete;
32	Singleton4& operator = (Singleton4 &&) = delete;
33	
34	static std::atomic<Singleton4*> _instance;
35	static std::mutex _mutex;
36};
37std::atomic<Singleton4*> Singleton4::_instance;
38std::mutex Singleton4::_mutex;
39

强制限制指令重排。

里面加入了获取屏障acquire fence和释放屏障release fence,也就后续的读/写操作不会重排到屏障前,且能读取其他线程的释放操作结果;释放屏障前面的读/写操作不会重排到屏障后,且保证当前线程的写入对其他线程可见。

示例5

1class Singleton5 {
2public:
3	static Singleton5* GetInstance() {
4		static Singleton5 instance;
5		return &instance;
6	}
7private:
8	Singleton5() {}
9	~Singleton5() {
10		std::cout << "~Singleton5()\n";
11	}
12	
13	Singleton5(const Singleton5 &) = delete;
14	Singleton5& operator = (const Singleton5&) = delete;
15	Singleton5(Singleton5 &&) = delete;
16	Singleton5& operator = (Singleton5 &&) = delete;
17};
18

这个版本是最简单的,如果只是让你简单实现一个单例模式,可以直接写这个版本。

示例6

1template<typename T>
2class Singleton {
3public:
4	static T* GetInstance() {
5		static T instance;
6		return &instance;
7	}
8protected:
9	Singleton() {}
10	virtual ~Singleton() {
11		std::cout << "~Singleton()\n";
12	}
13private:
14	Singleton(const Singleton &) = delete;
15	Singleton& operator = (const Singleton&) = delete;
16	Singleton(Singleton &&) = delete;
17	Singleton& operator = (Singleton &&) = delete;
18};
19
20class DesignPattern : public Singleton<DesignPattern> {
21	friend class Singleton<DesignPattern>;
22private:
23	DesignPattern() {}
24	~DesignPattern() {
25		std::cout << "~DesignPattern()\n";
26	}
27};
28

使用类模板把三个要点进行封装

总结:

版本1:堆上资源不能正确析构(没有调用析构函数)

版本2:堆上资源能正确析构(调用了析构函数)

版本3:双检查锁,可能造成内存泄露

版本4:线程安全,原子操作+互斥锁+内存屏障

版本5:C++11静态局部变量具备线程安全特性延迟加载内存正确释放

版本6:模板类封装了单例要点


设计模式——单例模式》 是转载文章,点击查看原文


相关推荐


Python 的内置函数 breakpoint
IMPYLH2025/10/2

Python 内建函数列表 > Python 的内置函数 breakpoint def breakpoint(): ''' 调用位置进入调试器 ''' Python 的内置函数 breakpoint() 是一个用于调试的便捷工具,它会在调用时自动触发调试器,让开发者能够暂停程序执行并检查当前状态。这个函数在 Python 3.7 及更高版本中引入,旨在简化调试过程,特别是在复杂程序中设置断点的场景。 基本用法: def calculate_sum(a, b):


Navicat导入Excel至瀚高数据库
瀚高PG实验室10/1/2025

解决方案:将Excel中没有数据行,执行删除操作。步骤五:将Excel分成若干部分导入,核实为数据库地址信息列字段长度设置小于实际数据长度。步骤三:导入显示成功,且显示行数与Excel行数一致。步骤四:导出数据与Excel进行比对,核实缺失数据。将缺失数据单独导入没有问题。解决客户使用Navicat导入Excel至瀚高数据库数据条数不一致问题。二、导入数据少于Excel数据行及Navicat导入结果显示行。步骤一:导入Excel,配置数据与Excel对应关系。一、导入数据多于Excel数据行。


在JavaScript / Node.js中,SQLite异步查询函数实现
Never_Satisfied9/30/2025

本文展示了一个Node.js中使用SQLite3的异步数据库查询实现。该方案通过Promise封装实现了参数化查询和异常处理,确保数据库连接始终安全关闭。核心功能包括:1)异步建立数据库连接;2)参数化SQL查询;3)使用try-finally保证连接关闭;4)完善的错误处理机制。实现特点包括Promise链式调用、async/await语法、连接关闭错误记录等。使用时需注意SQL参数匹配和错误捕获,该方案适用于需要安全访问SQLite数据库的Node.js应用场景。


在 Trae 国际版中添加 Chrome Dev MCP Server(Windows 实战指南)
wuhanwhite2025/10/2

前言 最近在折腾 Trae 国际版这类 AI 编程工具的时候,我发现它虽然支持接入 MCP(Model Context Protocol),但是只接受通过 npx 或 uvx 启动的 stdio 模式。 与此同时,Chrome 端的 mcp-chrome 扩展确实很好用,可以让 AI 直接获取浏览器标签页、截图、修改网页样式,甚至做一些自动化操作。但它默认暴露的是 Streamable HTTP 接口,并不符合 Trae 的要求。 这篇文章记录一下我在 Windows 环境下的实践


第一章 机器学习基础理论:机器学习概述(一)
FPGA+护理+人工智能2025/10/3

第一章 机器学习基础理论:机器学习概述 文章目录 第一章 机器学习基础理论:机器学习概述具体的专栏内容请参考: 人工智能专栏一、目标二、重点与难点三、内容1. 机器学习概述2. 机器学习在精神病护理领域的应用 前面python的基础内容算是完成了,接下来将要进入机器学习部分了。 具体的专栏内容请参考: 人工智能专栏 一、目标 通过本章学习,能够: 理解机器学习的基本概念和发展历程 了解机器学习在精神病护理领域的应用现状与前


免费领源码-Spring boot的物流管理系统 |可做计算机毕设Java、Python、PHP、小程序APP、C#、爬虫大数据、单片机、文案
vx_dmxq2112025/10/5

目   录 摘  要 Abstract 1  前言 1.1 设计目的 1.2 设计思路 1.3 国内外研究现状 2  相关技术 2.1  Java语言 2.2 MySQL数据库 2.3 Spring Boot框架 2.4 B/S模式 3  系统分析 3.1  可行性分析 3.2  系统需求分析 3.2.1  功能性分析 3.2.2  非功能性需求分析 3.3  系统用例分析 3.3.1  注


【转载】前验光师如何通过聪明模仿而非蛮干构建月收入3.5万美元的SaaS应用
是魔丸啊2025/10/6

转载 大多数人都认为你需要在科技领域拥有突破性的想法才能成功。 Samuel Rondot的想法与众不同。他的整个行动手册建立在一个简单的规则上:不要重新发明轮子——只要让它变得1%更好。 这种心态帮助他辞去了验光师的工作,从零开始自学编程,并推出了三个现在每月收入3.5万美元的SaaS应用。 以下是他如何做到的。 从验光师到自学程序员 Samuel从未计划成为一名程序员。几年前,他在眼镜行业工作,完全没有编程经验。 什么改变了?他想重建一个自己正在使用的Instagram工具——这一次,完全靠


apache POI 万字总结:满足你对报表一切幻想
大鱼七成饱2025/10/7

背景 国庆期间接了个兼职,处理机构的几张Excel报表。初次沟通,感觉挺简单,接入Easyexcel(FastExcel),然后拼lamda表达式就跑出来了。不过毕竟工作了这些年,感觉没这么简单。后面找业务方详细聊了一次,将需求落到纸面上。逐行研究了下BRD,有点挠头,跑数加各种样式,兼容新老版本,老方案是不行了。综合对比,最终选了老牌的 Apache POI 实现,下面说下为啥选POI,还有POI怎么用,包含样式、公式、动态表头、安全防范、百万级数据导入导出等功能。 一、技术选型 如果实现该


php artisan db:seed执行的时候遇到报错
快支棱起来2025/10/9

INFO Seeding database. Illuminate\Database\QueryException SQLSTATE[42S22]: Column not found: 1054 Unknown column 'email_verified_at' in 'field list' (Connection: mysql, SQL: insert into users (name, email, email_verified_at, password, remember_token,


C++ const 用法全面总结与深度解析
oioihoii2025/10/10

1. const 基础概念 const 关键字用于定义不可修改的常量,是C++中确保数据只读性和程序安全性的核心机制。它可以应用于变量、指针、函数参数、返回值、成员函数等多种场景,深刻影响代码的正确性和性能。 1.1 本质与编译期处理 const变量在编译时会被编译器严格检查,任何修改尝试都会导致编译错误。与C语言不同,C++中的const变量(尤其是全局const)通常不会分配内存,而是直接嵌入到指令中(类似#define),但在以下情况会分配内存: 取const变量地址时 const变量为

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0