基于C++的《Head First设计模式》笔记——单件模式

目录

一.专栏简介

二.引出单件模式

三.剖析经典的单件模式实现

四.单件模式的告白

五.定义单件模式

六.处理多线程

七.总结


一.专栏简介

本专栏是我学习《head first》设计模式的笔记。这本书中是用Java语言为基础的,我将用C++语言重写一遍,并且详细讲述其中的设计模式,涉及是什么,为什么,怎么做,自己的心得等等。希望阅读者在读完我的这个专题后,也能在开发中灵活且正确的使用,或者在面对面试官时,能够自信地说自己熟悉常用设计模式。

本章将开始单件模式的学习,也叫单例模式。

二.引出单件模式

单件模式编写的类只能实例化出仅"一个"对象,而且是唯一的对象。单件模式给我们一个全局的访问点,就像全局变量一样,但没有全局变量的缺点。如果把一个对象赋值给一个全局变量,那么这个对象可能在应用启动时就被创建。要是这个对象是资源密集型的,创建之后还从来没有被使用过,那就是一个比较大的浪费了。单件模式允许我们在需要的时候再创建这个单件对象。

我们怎么令一个类不能实例化?令这个类的构造函数私有。又怎么调用构造函数实例化一个对象呢?用这个类里面的静态函数调用这个构造函数。代码如下:

Singleton.h:

cpp 复制代码
#pragma once

class MyClass
{
public:
	static MyClass* getInstance();
private:
	MyClass();
};

Singleton.cpp:

cpp 复制代码
#include "Singleton.h"

MyClass* MyClass::getInstance()
{
	return new MyClass();
}

有了这一层认识,我们接下来剖析经典的单件模式实现。

三.剖析经典的单件模式实现

在写代码之前,我们先理解两个核心知识点,也是C++单件模式的灵魂。

  • 类内的静态成员变量,必须在类外初始化 → 是,C++ 硬性语法规则,无例外(除了 const static 整型 / 枚举);
  • 类外初始化静态成员时,可以合法调用类的私有构造函数 → 是,核心原理:类自身拥有访问所有私有成员的权限,静态成员属于类本身

我们首先编写一个懒汉模式的单件模式,也就是延迟实例化的版本。代码如下:

Singleton.h:

cpp 复制代码
#pragma once
#include <iostream>
using namespace std;

class LazySingleton
{
public:
	static LazySingleton* getInstance();
private:
	static LazySingleton* instance;

	LazySingleton();
	LazySingleton(const LazySingleton& another) = delete;
	LazySingleton& operator=(const LazySingleton& another) = delete;
};

Singleton.cpp:

cpp 复制代码
#include "Singleton.h"

LazySingleton* LazySingleton::instance = nullptr;

LazySingleton::LazySingleton()
{

}

LazySingleton* LazySingleton::getInstance()
{
	if (instance == nullptr)
	{
		cout << "第一次实例化" << endl;
		instance = new LazySingleton();
	}
	return instance;
}

首先是防构造,令构造函数成为私有函数,其次是防拷贝构造和赋值运算符重载,避免出现多个对象,这里运用C++11的delete关键字将它俩设置为删除状态。单例对象指针instance私有且在类外初始化为nullptr,getInstance()获取这个单例对象,第一次获取时进行初始化。

当然,这个类是有一些问题的,比如线程安全问题,我们后面细说。

下面再看看恶汉模式的代码实现:

Singleton.h:

cpp 复制代码
class HungrySingleton
{
public:
	static HungrySingleton* getInstance();
private:
	static HungrySingleton* instance;

	HungrySingleton();
	HungrySingleton(const HungrySingleton& another) = delete;
	HungrySingleton& operator=(const HungrySingleton& another) = delete;
};

Singleton.cpp:

cpp 复制代码
HungrySingleton* HungrySingleton::instance = new HungrySingleton();

HungrySingleton::HungrySingleton()
{

}

HungrySingleton* HungrySingleton::getInstance()
{
	cout << "获取饿汉实例" << endl;
	return instance;
}

与懒汉模式不同的地方在与,单例对象的指针在类外面进行初始化时不是初始化为nullptr,而是实例化出了这个单例对象,在程序启动时这个对象就存在了,持续到进程结束。

现在测试懒汉模式饿汉模式

四.单件模式的告白

"一个"的威力很强的。比如说,你有一个包含注册表设置的对象。你不想让这个对象有多个副本,到处赋值------这会导致混乱。通过使用像我这样的对象,你可以确保应用中的每个对象使用同一全局资源。

单件模式常常被用来管理资源池,像连接或者线程池

五.定义单件模式

单件模式确保一个类只有一个实例,并提供一个全局访问点。

六.处理多线程

上面的懒汉模式getInstance()函数 的代码不是线程安全的,单例对象是临界资源,我们并没有对它进行加锁保护

上面是书中用Java多线程场景下导致不同线程获得不同单件对象的例子,咱们C++方向的也能一下看懂。

每次一进入getInstance()后都马上加锁,那么并发就会降低很多。我们用"双重检查加锁"在getInstance()减少使用同步,代码如下:

cpp 复制代码
LazySingleton* LazySingleton::getInstance()
{
	if (instance == nullptr)
	{
		unique_lock<mutex> ul(_mutex);
		if (instance == nullptr)
		{
			cout << "懒汉第一次实例化" << endl;
			instance = new LazySingleton();
		}
	}
	cout << "获取懒汉实例" << endl;
	return instance;
}

这样,只有在第一次instance为空时才加锁并且实例化,大大减少了同步,提高了并发,并且线程安全

七.总结

本章篇幅较小,单例模式也较为简单,但很多细节还是需要我们注意。

相关推荐
自不量力的A同学41 分钟前
开源一站式智能终端 uniTerm v1.9.4 发布
笔记
风合星语1 小时前
2026 ROS 2 Lyrical C++ 入门(五):Action 实战——任务反馈、取消与超时处理
c++·机器人·ros2·异步编程·lyrical
91刘仁德2 小时前
C++ 继承和多态 设计模式
c语言·c++·笔记
库玛西2 小时前
哈夫曼树与前缀编码:数据压缩的贪心核心
c语言·c++·笔记·考研
别动我齐刘海2 小时前
ROS2 Jazzy + C++ 实战路线——进阶学习3
c++·人工智能·vscode·python·算法·机器学习·机器人
0+1112 小时前
算法 --滑动窗口
c++·算法·leetcode
stolentime3 小时前
(有原题)CSP-S2026第一轮试题(附答案解析、markdown源码)
c++·csp
m0_734571763 小时前
深入理解C++ RAII
开发语言·c++
hetao17338373 小时前
2026-09-18 hetao1733837 的刷题记录
c++·算法
stolentime4 小时前
CSP-J2026第一轮试题(附答案解析、markdown源码)
c++·csp