单例模式的C++实现

题目链接

1.单例模式简述

单例模式是⼀种创建型设计模式, 它的核⼼思想是保证⼀个类只有⼀个实例,并提供⼀个全局访问点来访问这个实例

2.单例模式的作用

单例模式提供了对唯一实例的受控访问,这样做可以避免多次创建相同对象,从而可以节省系统资源,而且多个模块可以通过单例实例共享数据

3.单例模式与全局变量的区别

单例模式是一种设计模式,保证一个类仅有一个实例,并提供一个全局访问点。它适用于需要全局使用且仅需要一个实例的情况,比如日志系统、线程池等。单例模式可以确保对象的唯一性,避免了多次实例化造成的资源浪费,并且可以提供全局访问点方便地对实例进行操作。然而,单例模式的实现可能涉及多线程安全、资源释放等问题,需要仔细设计和考虑。
全局变量是在程序中定义的可以被任何函数访问的变量。全局变量的优点是方便在程序的任何地方访问,可以节省参数传递的开销,提高程序的可读性。然而,过多地使用全局变量可能导致代码耦合性增加,使得程序难以维护和扩展。此外,全局变量的存在也增加了程序的不确定性,可能导致代码出现意外的副作用。
总的来说,单例模式适用于需要确保唯一实例的场景,可以避免多次实例化以及提供全局访问点的便利性,但需要考虑多线程安全等问题;而全局变量虽然方便访问,但可能导致耦合性增加和程序设计的复杂性,需要慎重使用。在实际应用中,需要根据具体情况来选择使用单例模式还是全局变量,以便兼顾代码的简洁性和可维护性。------《来自chatGPT》

简单来说,全局变量访问方便,但会紧耦合,另外全局变量一开始就会初始化,做不到懒加载(需要使用时再实例化,懒汉单例可以做到)。而单例需要考虑线程安全

3.单例模式的实现要求

  1. 私有的构造函数:防⽌外部代码直接创建类的实例;
  2. 私有的静态实例变量:保存该类的唯⼀实例;
  3. 公有的静态⽅法:通过公有的静态⽅法来获取类的实例;

考虑到线程安全和编译优化,情况可能会更加复杂。

4.懒汉单例的C++单线程版本

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

Singleton* Singleton::m_instance=nullptr;

Singleton* Singleton::getInstance(){
	if(m_instance == nullptr){	//多线程情况下不安全,假设线程A执行完这一行,但下一行还没执行,而恰好换线程B执行,那么A和B会同时进入下一行,那么会不止创建一个实例。
		m_instance = new Singleton();
	}
	return m_instance;
}

5.懒汉单例的C++多线程版本

5.1保证线程安全的加锁版本

...
Singleton* Singleton::getInstance(){
	Lock lock;	//lock是局部变量,函数结束会自动调用类的析构函数
	if(m_instance == nullptr){
		m_instance = new Singleton();
	}
	return m_instance;
}

锁的代价太高,getInstance本质上是读操作,读操作不应该是互斥的,也就是说m_instance一旦不为空,就应该允许多线程获取它。

5.2双检查锁版本

Singleton* Singleton::getInstance(){
	if(m_instance==nullptr){	//先检查再上锁
		Lock lock;
		if(m_instance == nullptr){
			m_instance = new Singleton();
		}	
	}
	
	return m_instance;
}

逻辑上,双检查锁是没问题的,一旦m_instance不为空,多线程都可以访问它,但可惜的是编译器优化会reorder,也就是汇编代码的顺序和高级语言代码顺序不是完全一致的,比如,new的过程会先分配内存,再执行类的构造函数,假设在分配完内存后,进行了线程切换,此时m_instance不是nullptr,但m_instance代表的对象可能还没有构造好就被使用了。因此双检查锁也还不是线程安全的,于是有了下面的最终版。

5.2最终版(C++11版本之后才有的跨平台实现)

std::atomic<Singleton*> Singlton::m_instance;	//声明一个原子对象
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance(){
	Singleton* tmp = m_instance.load(std::memory_order_relaxed);
	std::automic_thread_fence(std::memory_order_acquire); //获取内存fence,屏蔽编译器的reorder, tmp就不会被reorder了	
	if(tmp == nullptr){
		std::lock_guard<std::mutex> lock(m_mutex);
		tmp = m_instace.load(std::memory_order_relaxed);
		if(tmp == nullptr){
			tmp = new Singleton;
			std::atomic_thread_fence(std::memory_order_release); //释放内存fence
			m_instace.store(tmp, std::memory_order_relaxed);
		}
	}
	return tmp;
}

注解:
automic_thread_fence的介绍,在其它语言中,如Java中不用这么复杂,给m_instance加个volatile关键字就行了,VC下也可以,但不是跨平台的。

public class Singleton {
	private static volatile Singleton instance;
	private Singleton() {
	// 私有构造⽅法,防⽌外部实例化
	}
	public static Singleton getInstance() {
		if (instance == null) {
			synchronized (Singleton.class) {
				if (instance == null) {
					instance = new Singleton();
				}
			}
		}
		return instance;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值