基于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为空时才加锁并且实例化,大大减少了同步,提高了并发,并且线程安全

七.总结

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

相关推荐
Howrun7772 小时前
虚幻引擎_用户小控件_准星
c++·游戏引擎·虚幻
CoderCodingNo2 小时前
【GESP】C++六级考试大纲知识点梳理, (1) 树的概念与遍历
开发语言·c++
刀法孜然2 小时前
23种设计模式 3 行为型模式 之3.6 mediator 中介者模式
设计模式·中介者模式
星火开发设计2 小时前
C++ multimap 全面解析与实战指南
java·开发语言·数据结构·c++·学习·知识
Naiva2 小时前
中微1000w储能逆变控制方案
笔记·方案·储能·逆变
丝斯20112 小时前
AI学习笔记整理(45)——大模型数据读取技术与模型部署
人工智能·笔记·学习
李日灐2 小时前
C++STL:deque、priority_queue详解!!:详解原理和底层
开发语言·数据结构·c++·后端·stl
你要飞2 小时前
考研线代第三课:向量组
笔记·线性代数·考研·矩阵
Lv11770082 小时前
Visual Studio中的正则表达式
ide·笔记·正则表达式·c#·visual studio
羑悻的小杀马特2 小时前
etcd实战指南:从安装集群到C++封装,解锁分布式服务治理的“钥匙”
c++·分布式·etcd·集群