C++ 单例模式

一、设计模式

设计模式:代表的是最佳实践,是软件开发过程中面临一般问题的解决方案,是面向对象语言所共有的。(类似于打仗时,使用的兵法)。设计模式一共有二十三种,本文重点介绍单例模式。

常见的设计模式:

适配器模式、迭代器模式、工厂模式、单例模式、生成器模式、装饰器模式、门面模式、代理模式、桥接模式

适配器模式

在之前学习栈和队列时,我们发现栈和队列的底层,并非自己实现,而是通过调用栈和队列(库中默认是双端队列)来实现的;以栈为例:我们在使用时不在乎,栈的底层实现,只要它能够满足尾插和尾删即可。通过这种方法,更好的体现了STL库的良好的复用性

迭代器模式

迭代器是STL库中算法和容器的纽带,使用迭代器便可以用同一种方法对不同的容器进行访问;迭代器类似与指针。迭代器的存在很好的保护了,面向对象的封装性,减少了使用成本。

假如在没有迭代器的情况下,如果要访问红黑树的某一个结点时,就必须满足以下条件:

1、必须把其根节点暴露出来(这样破坏了其的封装性)

2、树的访问往往是通过遍历来实现,不能直接访问某个结点

3、要访问某个结点,必须了解其底层实现(学习成本太大,不够方便)

二、单例模式(Singleton)

1、概念

一个类始终只能创建一个实例,则这个类被称为单例类,这种模式就被称为单例模式。(线程安全且高效)

2、特点

  • 单例类保证全局只有一个唯一实例对象
  • 单例类提供获取这个唯一实例的接口(GetInstance :获得实例)

3、实现

方法一、不考虑线程安全
#define _CRT_SECURE_NO_WARNIN  1
#pragma once
class Singleton
{
public:
	static Singleton* GetInstance() //定义成静态方法,使得这个属性属于整个类  
	{
		if(_inst == NULL)  //保证只有一份
		{
			_inst = new Singleton;  
		}
		return _inst;
	}
	void Print()
	{
		printf("%d\n",_a);
	}
private:
	Singleton()  //必须自己定义构造函数,且设为私有
	 :_a(0)      //1.防止调用默认的构造函数  2、防止利用构造函数创建对象
	{
	}
	Singleton(const Singleton&); //防拷贝,只声明不定义
    Singleton& operator=(const Singleton&);   //防止进行深浅拷贝
private:
  	int _a; 
    static Singleton* _inst;    //静态变量全局只存一份 ,此处的变量可以是 threadpool memorypool
};
Singleton* Singleton:: _inst = NULL; //静态变量必须在类外面,进行初始化

注意:

单例类的名字可以根据实际使用适当的名字,而非只能使用singleton;

单例类中的变量可以是线程池、内存池等(全局只有一份)等;

赋值运算符的重载不一定要设为私有,只在在一些情况下会存在深浅拷贝问题,因此最好设为私有;

方法二、考虑线程安全

上面所实现的单例类,在多线程的情况下,会出现线程安全问题

主要是由于下面的代码引起:

static Singleton* GetInstance()  
	{
		if(_inst == NULL)  
		{
			_inst = new Singleton;
		}
		return _inst;
	}
在多核情况下:

当多个线程同时执行这段代码时,每个线程都创建了一个对象;这样便不是全局唯一的实例对象了;

在只有一个CPU 的情况下:

说明:每一个被CPU调度的线程,只能在时间片内执行;

由于以上代码不是原子的,假设有两个线程执行上述代码:

首先线程一被调度 :在执行完 if(_inst == NULL) ,时间片用完了;这时线程二被调度: 在执行完  new Singleton ,时间片用完了;线程一被再次调度,会执行 _inst = new Singleton ,那么此时线程一创建了一个对象,并且线程二创建的对象被丢弃了,这时出现了问题。

普通版:
class Singleton
{
public:
	static Singleton* GetInstance() 
	{
		_mtx.lock(); //加锁
		if(_inst == NULL)  
		{
			_inst = new Singleton;
		}
		_mtx.unlock(); //解锁
		return _inst;
	}
	void Print()
	{
		printf("%d\n",_a);
	}
private:
	Singleton()  
	 :_a(0)     
	{
	}
	Singleton(const Singleton&); 
    Singleton& operator=(const Singleton&);   
private:
  	int _a; 
    static Singleton* _inst;   
    static mutex _mtx;  //只有一个锁
};
mutex Singleton::_mtx ; //调用mutex的构造函数
Singleton* Singleton:: _inst = NULL; 

上面所实现的单例类,存在一定的缺陷。如果在锁之间的代码出现异常时,便会导致没有解锁,导致大量的线程阻塞在_mtx.lock() 处,进而可能会出现死锁。出现死锁便是很危险的事情。可以对锁资源,利用RAII思想来解决此问题。

RAII:资源获得即初始化,资源在构造函数时初始化,自动调用析构函数,释放资源。

class Lock()
{
 public:
	Lock(mutex& mtx) 
		:_mtx(mtx)  //引用必须在初始化列表中初始化
	{
		_mtx.lock();
	}
	~Lock()
	{
		_mtx.unlock();
	}
 private:
	mutex& _mtx;  //保证只有一把锁
}
优化版:
class Singleton
{
public:
	static Singleton* GetInstance() 
	{
		Lock lock(_mtx);  //使用RAII思想
		if(_inst == NULL)  
		{
			_inst = new Singleton;
		}
		return _inst;
	}
	void Print()
	{
		printf("%d\n",_a);
	}
private:
	Singleton()  
	 :_a(0)     
	{
	}
    Singleton(const Singleton&); 
    Singleton& operator=(const Singleton&);   
private:
    int _a; 
    static Singleton* _inst;   
    static mutex _mtx;  
};
mutex Singleton::_mtx ; 
Singleton* Singleton:: _inst = NULL; 
注意:

1.此处可以自己通过RAII思想实现一个锁,在C++11中提供了lock_guard类,也可以实现自动释放

2.lock_guard是一个模板类,因为不同的平台中互斥资源的命名不同,例如:在windows中称为临界资源 、在Linux中称为mutex(本文用到的是mutex)

方法三、考虑安全和效率

方法二虽然解决了线程安全,但并不高效。

高效版:
class Singleton
{
public:
	static Singleton* GetInstance() 
	{
		
		if(_inst == NULL)    //双检查
		{
			lock_guard<mutex> lock(_mtx);
			if(_inst == NULL)
			{
			  _inst = new Singleton;
			}
		}
		return _inst;
	}
	void Print()
	{
		printf("%d\n",_a);
	}
private:
	Singleton()  
	 :_a(0)     
	{
	}
	Singleton(const Singleton&); 
    Singleton& operator=(const Singleton&);   
private:
  	int _a; 
    static Singleton* _inst;   
    static mutex _mtx;  //只有一个锁
};
mutex Singleton::_mtx ; //调用mutex的构造函数
Singleton* Singleton:: _inst = NULL; 
单例模式分类:

  • 懒汉模式 lazy load : 在调用GetInstance()时才创建对象
  • 饿汉模式 一开始 load : 一开始就创建

由分类可知:上面的单列模式都属于懒汉模式。

饿汉模式:

class Singleton
{
public:
	static Singleton* GetInstance()  //此处也可以返回对象的引用
	{
		assert(_inst);
		return _inst;
	}
	void Print()
	{
		printf("%d\n",_a);
	}
private:
	Singleton()  
	 :_a(0)     
	{
	}
	Singleton(const Singleton&); 
        Singleton& operator=(const Singleton&);
	static Singleton* _inst = new Singleton;  
private:
    int _a;  
    static mutex _mtx; 
};
mutex Singleton::_mtx ; 
比较:

懒汉模式:相对复杂,但在各种场景下都使用;

饿汉模式:简结,但适用性有限制 例如:动态库:只有在运行时才加载





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值