C++设计模式-单例(1)
C++设计模式-单例(1)
设计模式:在解决某一类问题场景时,有既定的,优秀的代码框架可以直接使用,其优势如下:
- 代码更易于维护,代码的可读性,复用性,可移植性,健壮性会更好
- 当软件原有需求有变更或者增加新的需求时,合理的设计模式的应用,能够做到软件设计要求的
开-闭原则
,即对修改关闭,对扩展开放,使软件原有功能修改,新功能扩充非常灵活- 合理的设计模式的选择,会使软件设计更加模块化,积极的做到软件设计遵循的根本原则
高内聚,低耦合
软件设计开-闭原则
:
开闭原则是软件设计中的一个重要原则,指的是软件实体(如类、模块、函数等)应该对扩展开放,对修改关闭。这意味着在不修改现有代码的基础上,通过扩展新功能来满足新的需求,从而提高软件的灵活性和可维护性
对扩展开放:允许在现有系统中添加新功能
对修改关闭:不需要修改现有的代码来添加新功能,避免引入新错误
通过遵循开闭原则,可以提高代码的可复用性和稳定性。常用的方法包括使用抽象类、接口以及多态等技术
创建型设计模式:
创建型设计模式关注对象的创建过程,旨在抽象实例化的过程。以下是几种常见的创建型设计模式
工厂方法模式(Factory Method Pattern)
抽象工厂模式(Abstract Factory Pattern)
单例模式(Singleton Pattern)
生成器模式(Builder Pattern)
原型模式(Prototype Pattern)
一、单例模式
1.相关概念
单例模式(Singleton Pattern
)是一种创建型设计模式,旨在确保一个类只有一个实例,并提供一个全局访问点
特点:
唯一性:一个类只能有一个实例
全局访问点:提供一个全局的访问点,可以访问到该实例
2.应用场景
单例模式适用于以下场景:
需要控制资源的访问:如数据库连接池、日志记录器等
需要全局唯一实例的类:如配置类、缓存类等
需要延迟实例化的类:如需要在程序运行期间根据需要创建实例,而不是在程序启动时创建
3.单例模式实现方法
常见的方法有一下几种
饿汉式
- 在类加载时就创建实例
- 简单,但在类加载时就初始化实例,可能造成资源浪费
懒汉式
- 在第一次使用时才创建实例
- 需要考虑线程安全问题
双重检查锁
- 结合懒汉式和同步块,确保线程安全并减少同步开销
静态内部类
- 利用类加载机制,保证线程安全和延迟加载
二、实现
1.饿汉式
饿汉式的单例模式,在还未获取类实例对象之前,类实例对象就已经产生了,需要注意如下几点:
限制构造函数的访问方式
- 构造函数私有化
定义一个唯一的类实例对象
- 私有化,通过
static
修饰定义一个静态接口函数,获取唯一的类实例对象
- 接口函数
return
唯一实例对象地址复制构造函数/赋值运算符均禁止(显示禁止)
代码实例:
Singleton.h
1 |
|
Singleton.cpp
类的静态变量初始化需要放到源文件(CPP
)中,如果在头文件中初始化静态变量,当该头文件被多个源文件包含时,会导致重复定义的问题。而在源文件中初始化则可以避免这个问题
1 |
|
测试代码:
通过接口函数,获取三个实例对象,将其实例对象的地址进行输出,可知地址均相同,表示单例模式下,只能得到该类的一个实例对象
1 |
|
运行结果:
1 | /home/zxz/Proj/C_C++/DesignPatterns/cmake-build-debug/DesignPatterns |
重要知识点:
static
修饰的变量在进程内存静态数据区(数据段),数据段中的变量(包括全局变量、静态变量和常量)在程序的main
函数执行之前初始化。这是因为这些变量在程序加载到内存时就已经被分配和初始化,以确保它们在main
函数以及其他任何代码执行之前就已经处于准备好的状态- 饿汉式单例模式,是线程安全的。在类加载的时候,静态成员
instance
就会被初始化并创建实例。这保证了实例在多线程环境中是安全的,因为类加载机制是由编译器和JVM
保证的
优缺点:
优点:
- 线程安全:饿汉式单例在类加载时创建实例,类加载过程是线程安全的。
- 实现简单:不需要额外的同步机制,代码实现简单
缺点:
- 资源浪费:即使没有使用单例实例,实例也会在类加载时创建。如果实例的创建依赖于外部资源或耗时操作,会导致程序启动变慢
- 不灵活:在一些场景下,可能需要在第一次使用时才创建实例(懒加载),饿汉式单例不适合这种情况
2.懒汉式
类唯一的实例对象直到第一次获取的时候才产生
- 在饿汉式的基础上,将
私有的唯一的类的实例对象
定义为指针对象
,并且在类外进行初始化为nullptr
1 | // Singleton.h |
- 并且静态函数接口获取唯一的实例对象时,先对实例对象进行判断,若为
nullptr
,则动态创建一个类实例对象;否则返回该类唯一的实例对象地址 (第一点非重点,第二点才是重点)
1 | // 定义一个静态的接口函数,获取唯一的类实例对象 |
代码实例:
Singleton.h
1 | class Singleton |
Singleton.cpp
1 | Singleton *Singleton::instance = nullptr; |
重要知识点:
- 懒汉模式的单例模式,不是线程安全的,因为懒汉式的静态接口函数非可重入函数
- 可重入函数:在多线程的条件下,可被多个线程调用并且不会产生竞态条件,可以被多个线程重复调用,而不会发生线程安全问题
- 假设,线程1,第一次进入
getInstance
函数,并且构造对象,但是还并未给instance
进行赋值,此时instance==nullptr
还是成立;若此时CPU
时间片被线程2抢占,进入getInstance
函数,构造对象,并且给instance
进行赋值,返回instance
指针变量中的地址。线程1,继续抢占CPU
时间片,并且给instance
进行赋值,最后返回。这时由于getInstance
函数的不可重入性,出现了线程安全问题,导致为该类创建了两个实例对象
3.线程安全的懒汉式
在懒汉模式的基础上,对临界区代码段,创建互斥锁,保证原子操作
(1)基础实现
Singleton.h
1 | class Singleton |
Singleton.cpp
1 | Singleton *Singleton::instance = nullptr; |
重要知识点:
但是这样粗暴的加锁方式,会导致在单线程的环境下,也会频繁加锁,加锁的力度过大
(2)双重检查锁实现(重点)
保证在单线程的环境下,每次调用getInstance
函数不需要重复进行加锁
Singleton.h
1 | class Singleton |
Singleton.cpp
1 | Singleton *Singleton::instance = nullptr; |
(3)静态内部类实现
Singleton.h
1 | class Singleton |
Singleton.cpp
1 | Singleton Singleton::SingletonHolder::instance; |
测试代码
1 |
|
(4)局部静态变量实现(重点)
《Effective C++》(Item 04)
中的提出另一种更优雅的单例模式实现,使用函数内的局部静态对象,这种方法不用加锁和解锁操作
Singleton.h
1 | class Singleton |
Singleton.cpp
1 | // 不需要做什么 |
测试代码
1 |
|