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

七.总结

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

相关推荐
_F_y5 小时前
MySQL用C/C++连接
c语言·c++·mysql
兩尛5 小时前
c++知识点2
开发语言·c++
xiaoye-duck5 小时前
C++ string 底层原理深度解析 + 模拟实现(下)——面试 / 开发都适用
开发语言·c++·stl
Azure_withyou6 小时前
Visual Studio中try catch()还未执行,throw后便报错
c++·visual studio
琉染云月6 小时前
【C++入门练习软件推荐】Visual Studio下载与安装(以Visual Studio2026为例)
c++·visual studio
不会代码的小猴6 小时前
Linux环境编程第六天笔记--system-V IPC
linux·笔记
乌恩大侠6 小时前
【笔记】USRP 5G 和 6G 参考架构
笔记·5g
biuyyyxxx7 小时前
Python自动化办公学习笔记(一) 工具安装&教程
笔记·python·学习·自动化
舟舟亢亢7 小时前
Java集合笔记总结
java·笔记
L_09078 小时前
【C++】高阶数据结构 -- 红黑树
数据结构·c++