三、道阻且长之单例模式

单例模式

在这里插入图片描述

首先什么是单例模式:
《设计模式》一书中给出的定义是:让类自身负责保存它的唯一实例,这个类可以保证没有其他实例被创建(通过截取创建新对象的请求),并且它可以提供一个访问该实例的方法,这就是Singleton模式。有人叫单例模式,也有叫单件模式。那么如何实现以上要求呢?首先,需要保证一个类只有一个实例;在类中,要构造一个实例,就必须调用类的构造函数,如此,为了防止在外部调用类的构造函数而构造实例,需要将构造函数的访问权限标记为protected或private;最后,需要提供要给全局访问点,就需要在类中定义一个static函数,返回在类内部唯一构造的实例。

####单例大约有两种实现方法:懒汉与饿汉。
懒汉:故名思义,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化;
饿汉:饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。
###特点与选择:
由于要进行线程同步,所以在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。
在访问量较小时,采用懒汉实现。这是以时间换空间。
###C++代码实现

  • 经典版:
#include <iostream>
using namespace std;
 //懒汉模式
class Singleton
{
public:
/**
*需要提供要给全局访问点,就需要在类中定义一个static函数,返回在类内部唯一构造的实例
*/
	static Singleton *GetInstance()
	{
		if (m_Instance == NULL )
		{ m_Instance = new Singleton ();
		}
		return m_Instance;
	} static void DestoryInstance()
	{
		if (m_Instance != NULL )
		{ delete m_Instance; m_Instance = NULL ;
		}
	}

private:
/**
*构造函数卸载私有里,为了防止在外部调用类的构造函数而构造实例
*/
	Singleton();
	static Singleton *m_Instance;
};
 
Singleton *Singleton ::m_Instance = NULL;
 
int main(int argc , char *argv [])
{
	Singleton *singletonObj = Singleton ::GetInstance(); 
	Singleton ::DestoryInstance();
	return 0;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

这是最简单,也是最普遍的实现方式,也是现在网上各个博客中记述的实现方式,但是,这种实现方式,有很多问题,比如:没有考虑到多线程的问题,在多线程的情况下,就可能创建多个Singleton实例,以下版本是改善的版本。

  • 经典版优化:-
#include <iostream>
using namespace std;
 //懒汉模式
class Singleton
{
public:
/*
此处进行了两次m_Instance == NULL的判断,是借鉴了Java的单例模式实现时,使用的所谓的“双检锁”机制。
因为进行一次加锁和解锁是需要付出对应的代价的,而进行两次判断,就可以避免多次加锁与解锁操作,同时也
保证了线程安全。但是,这种实现方法在平时的项目开发中用的很好,也没有什么问题?但是,如果进行大数据
的操作,加锁操作将成为一个性能的瓶颈;为此,一种新的单例模式的实现也就出现了。
*/
	static Singleton *GetInstance()
	{
		if (m_Instance == NULL )
		{ Lock(); if (m_Instance == NULL ) { m_Instance = new Singleton (); } UnLock(); }
		return m_Instance;
	} static void DestoryInstance()
	{
		if (m_Instance != NULL )
		{ delete m_Instance; m_Instance = NULL ;
		}
	}
private:
	Singleton();
	static Singleton *m_Instance;
};
 
Singleton *Singleton ::m_Instance = NULL;
 
int main(int argc , char *argv [])
{
	Singleton *singletonObj = Singleton ::GetInstance();
	Singleton ::DestoryInstance();
	return 0;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 内部静态变量的懒汉实现:-
    此方法也很容易实现,在instance函数里定义一个静态的实例,也可以保证拥有唯一实例,在返回时只需要返回其指针就可以了。推荐这种实现方法,真得非常简单。
#include <iostream>
using namespace std;
 
class Singleton
{
public:
	static Singleton *GetInstance()
	{
		lock();
		static Singleton m_Instance;
		unlock();
		return &m_Instance;
	} 
private:
	Singleton();

};
 
int main(int argc , char *argv [])
{
	Singleton *singletonObj = Singleton ::GetInstance();
	cout<<singletonObj->GetTest()<<endl; singletonObj = Singleton ::GetInstance();
	cout<<singletonObj->GetTest()<<endl;
}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

因为static Singleton *GetInstance()是不可重入函数,所以存在线程安全问题,访问静态变量时要加锁;那么一个函数要成为可重入函数,必须要具备以下几个特点:
1、仅依赖于调用方传入的参数
2、不依赖于任何单个资源的锁
3、不反回任何(局部)静态或全局的非const变量的指针
4、不使用任何(局部)静态或全局的非const变量
5、不调用任何不可重入函数
可重入是并发安全的强力保证,一个可重入函数可以在多线程环境下放心使用。

  • 饿汉实现:-
#include <iostream>
using namespace std;
 
class Singleton
{
public:
	static Singleton *GetInstance()
	{
		return m_instace;
	} 
private:
	Singleton();
	static Singleton *m_instance;

};
Singleton* Singleton :: m_instance = new Singleton();
int main(int argc , char *argv [])
{
	Singleton *singletonObj = Singleton ::GetInstance();
	singletonObj = Singleton ::GetInstance();
	return 0;
}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

因为静态初始化在程序开始时,也就是进入主函数之前,由主线程以单线程方式完成了初始化,所以静态初始化实例保证了线程安全性。在性能要求比较高时,就可以使用这种方式,从而避免频繁的加锁和解锁造成的资源浪费。

  • 实例销毁:-
#include <iostream>
using namespace std;
 
class Singleton
{
public:
	static Singleton *GetInstance()
	{
		return m_Instance;
	} int GetTest()
	{
		return m_Test;
	}
 
private:
	Singleton(){ m_Test = 10; }
	static Singleton *m_Instance;
	int m_Test; // This is important
	class GC
	{
	public :
		~GC()
		{ // We can destory all the resouce here, eg:db connector, file handle and so on if (m_Instance != NULL ) { cout<< "Here is the test" <<endl; delete m_Instance; m_Instance = NULL ; }
		}
	};
	static GC gc;
};
 
Singleton *Singleton ::m_Instance = new Singleton();
Singleton ::GC Singleton ::gc;
 
int main(int argc , char *argv [])
{
	Singleton *singletonObj = Singleton ::GetInstance();
	cout<<singletonObj->GetTest()<<endl; return 0;
}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

文章来源: blog.csdn.net,作者:IM-STONE,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/doubleintfloat/article/details/79824804

(完)