【C++】设计模式之——单例模式

本文详细介绍了C++中的单例模式,包括实现注意事项、模式结构图以及两种常见的实现方式——饿汉模式和懒汉模式。讨论了饿汉模式的内存浪费与线程安全,以及懒汉模式的线程不安全问题及其解决方案,通过使用volatile关键字确保线程安全和有序性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

保证一个类仅有一个实例对象,并且提供一个访问它的全局访问点,并且使用静态数据成员来表达这唯一的一个对象。可以类比于windows的任务管理器,无论多少次打开,始终只显示一个窗口。

一、实现单例模式需要注意的点:

         (1)屏蔽构造和拷贝构造函数;

         (2)在类中提供一个接口生成唯一的对象,不能返回类类型;

         (3)不能依赖对象调用;

         实现单例解决的问题是怎样创建一个唯一的对象?

二、单例模式结构图:

单例模式典型的结构图。在Singleton 模式的结构图中可以看到,通过维护一个 static 的成员变量来记录这个唯一的对象实例。通过提供一个 staitc 的接口 instance 来获得这个唯一的实例。

三、单例模式的实现:

单例模式下的对象加载又分为懒汉模式和饿汉模式。

1、懒汉模式:第一次用到类实例的时候才会去实例化,也称延时加载;

2、饿汉模式:在单例类定义的时候就进行了实例化,也称贪婪加载;

四、两者的具体如下:

1、饿汉模式实现:符合贪婪加载即就是提前加载;

缺点:当提前加载好的对象在后期没有用到的时候会造成内存浪费;

优点:不存在线程安全问题;

class Singleton
{
public:
    static Singleton* GetInstance()
    {
        return single;
    }
private:
    Singleton(){};
    Singleton(const Singleton&);
    static Singleton *single;
};
Singleton *Singleton::single = new Singleton();

3、懒汉模式的实现:符合延时加载即就是用时才生成;

缺点:存在线程安全的问题;

为什么是线程不安全呢?

假设我们有两个线程,线程1进入要创建实例对象,当线程1将要创建时,cpu切换给了线程2,此时线程2也要创建实例对象,single为空,线程2成功创建了对像后。Cpu切换给了线程1,线程1继续进行对象的创建,这样最终会产生2个对象。这种结果就不是我们单例模式所要实现的目标。因此说它是线程不安全的。如下图所示:

那么,我们可以通过锁机制来实现线程安全,而且必须用二重锁机制。

class Singleton
{
public:
	static Singleton* GetInstance()
	{
		if(single == NULL)
		{
			//lock()
			if(single == NULL)
			{
				single = new Singleton();
			}
			//unlock();
		}
		return single;
	}
private:
	Singleton(){};
	Singleton(const Singleton&);
	static Singleton *single;
};
Singleton *Singleton::single = NULL;

但是对于懒汉模式下的二重锁机制加载也会存在一个微小的问题:就是在single = new Singleton();时;因为new不是一个原子操作,在进行new的时候会有以下三步:

(1)分配内存空间;(2)调用构造函数进行初始化 (3)将定义的对象single指向刚分配的内存空间

但是在计算机系统中为了提高计算机系统性能,编译器,处理器,缓存会对程序指令和数据进行重排序,而对象的初始化操作并不是一个院子操作。因此上述的三步操作可能会被重排序为:(1) (3) (2)

所以可能会存在这样的情况:一个线程正在构造对象的过程中(构造方法还未调用),另一个线程检查时看见了single为非空。也就是说对象可能会被非安全发布(对象并不完整就被其他线程所使用)。

解决这一问题:使用volatile关键字,这个关键字的作用就是禁止编译器优化;此时,系统就被禁止进行重排序,所有的写操作都将发生在读操作之前。

volatile static Singleton* single;//标识唯一对象;

volatile关键字能保证可见性和有序性;

在这里其保证了有序性,两层含义:

第一当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;

第二在进行指令优化时,不能将在对volatile变量的读操作或者写操作的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值