设计模式之单例模式
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式之单例模式相关的知识,希望对你有一定的参考价值。
在平时写程序的时候往往会遇到这样的需求,对于某些资源我们只想让其只能由一个对象进行访问,从而保证其完整性。比如,配置文件,工具类,线程池,缓存,日志对象等。对这些资源进行访问的对象我们只需要一个,当能对其进行读写的对象多了的时候就可能由于逻辑上的问题导致了很多意想不到的结果。在这个的背景下,结合了面向对象的思想,单例模式就出来了。
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个实例,一个好的办法是让类负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建。并且它可以提供一个访问该实例的方法。
基于以上的需求和思想,将单例模式在C++上实现如下:
1 //Singleton.h头文件 2 #pragma once 3 #ifndef SINGLETON_H 4 #define SINGLETON_H 5 /* 6 * author:caoshuping 7 * date:2016.03.14 8 * 说明:单例模式在C++中的实现 9 * 实现步骤: 10 * 1.将构造方法私有化,防止在类外进行实例化。 11 * 2.声明静态指针,指针指向Singleton,也就是指向唯一可以访问对象的资源实例的指针 12 * 3.提供唯一的获得实例的结构GetInstance() 13 * 类型:饿汉模式 and 懒汉模式 14 */ 15 class Singleton 16 { 17 private: 18 Singleton(); 19 static Singleton *m_pInstance; 20 public: 21 static Singleton * GetInstance(); 22 ~Singleton(); 23 }; 24 #endif 25 26 //Singleton.cpp实现文件 27 #include "Singleton.h" 28 #include <iostream> 29 30 using namespace std; 31 32 //类中的静态成员变量必须在类外进行初始化,不然会报link error 33 Singleton *Singleton::m_pInstance = NULL; 34 //饿汉模式。也就是在类加载时候就对静态成员变量进行初始化。 35 //Singleton *Singleton::m_pInstance = new Singleton(); 36 37 Singleton::Singleton() 38 { 39 m_pInstance = NULL; 40 } 41 Singleton::~Singleton() 42 { 43 } 44 /* 45 * 单例模式的唯一接口,使外部可以获得唯一的指向资源的对象实例。 46 */ 47 Singleton *Singleton::GetInstance() 48 { 49 //判断是否为空,为空的话进行初始化 50 //这种模式的单例模式为懒汉模式。也就是只有第一次需要使用这个对象实例的时候才对对象进行初始化 51 //当对静态成员变量m_pInstance在类外直接调用new Singleton()进行对象初始化。这种为饿汉模式。可以看第8,9行代码 52 if (m_pInstance == NULL) 53 m_pInstance = new Singleton(); 54 return m_pInstance; 55 }
单例类CSingleton有以下特征:
1.它有一个指向唯一实例的静态指针m_pInstance,并且是私有的;
2.它有一个公有的函数,可以获取这个唯一的实例,并且在需要的时候创建该实例;
3.它的构造函数是私有的,这样就不能从别处创建该类的实例。
但是以上的实现还是有问题的。问题出现在指针。在C\C++中指针是柄双刃剑,在带来方便,实现一些magic的效果以外,也会带来很多麻烦,最明显的当属内存泄露(memory leak)。对于以上的问题内存泄露的问题依然是存在。当然有人会说,由于m_pInstance是静态变量,会保存在全局数据区,当程序运行结束的时候自然会释放,即使我们在GetInstance中new了不显示的delete也没有关系。但是,以上只是一个简单的实例。当我们想对访问日志对象设计单例模式的时候情况就不一样了。由于日志对象时一个文件,在对日志文件进行访问的时候就要保证一个文件句柄。而对文件的占用并不会随着程序的结束而终止。所以此时必须要对文件句柄进行显示的释放。
怎么在程序结束的时候对其释放尼。基本的思路是定义一个内部类,然后再类内部定义一个内部类的对象,这样在程序结束的时候,在释放内部类对象的时候会调用内部类的析构函数,我们就可以将所有的释放操作放在内部类的析构函数中进行。
所以单例模式的C++实现v2.0版本如下:
1 #pragma once 2 #ifndef SINGLETON_H 3 #define SINGLETON_H 4 #include<iostream> 5 using namespace std; 6 /* 7 * author:caoshuping 8 * date:2016.03.14 9 * 说明:单例模式在C++中的实现 10 * 实现步骤: 11 * 1.将构造方法私有化,防止在类外进行实例化。 12 * 2.声明静态指针,指针指向Singleton,也就是指向唯一可以访问对象的资源实例的指针 13 * 3.提供唯一的获得实例的结构GetInstance() 14 * 类型:饿汉模式 and 懒汉模式 15 */ 16 class Singleton 17 { 18 private: 19 Singleton(); 20 21 //垃圾回收类,这个类主要功能是在其析构函数中释放m_pInstance所占用的资源 22 class CGarbo 23 { 24 public : 25 //在析构函数中释放m_pInstance所占用的资源 26 ~CGarbo() 27 { 28 if (m_pInstance != NULL) 29 delete m_pInstance; 30 }; 31 }; 32 //在Singleton中定义CGarbo的静态变量,这样在程序结束的时候程序会销毁程序中使用的静态变量, 33 //在销毁garbo对象的时候执行CGarbo类的析构函数,从而实现了对m_pInstance的释放 34 static CGarbo garbo; 35 static Singleton *m_pInstance; 36 public: 37 static Singleton * GetInstance(); 38 ~Singleton(); 39 }; 40 #endif
cpp文件中内容没有改变,主要改变在头文件中。
类CGarbo被定义为CSingleton的私有内嵌类,以防该类被在其他地方滥用。
程序运行结束时,系统会调用CSingleton的静态成员Garbo的析构函数,该析构函数会删除单例的唯一实例。
使用这种方法释放单例对象有以下特征:
1.在单例类内部定义专有的嵌套类;
2.在单例类内定义私有的专门用于释放的静态成员;
3.利用程序在结束时析构全局变量的特性,选择最终的释放时机;
4.使用单例的代码不需要任何操作,不必关心对象的释放。
好了,现在在单例模式在C++语言中的实现基本完成,在大多数情况下不会有什么问题,其他的就是视具体情况来定了。那么下面就是要解决单例模式的线程安全问题。
在多线程的程序中,当多个线程调用GetInstance()函数时就有可能产生多个实例,也就new Singleton()会执行很多次。此时就违背了单例模式的最初设计思想了。所以在多线程的环境中我们需要进行加锁操作。保证m_pInstance只有个实例。
我们可以对new Singleton进行加锁操作,从而保证多线程时的安全。
C++实现如下:
1 //Lock.h文件 2 #pragma once 3 #ifndef CLOCK_H 4 #define CLOCK_H 5 #include<afxmt.h> 6 class CLock 7 { 8 private : 9 CCriticalSection m_cs; 10 public: 11 CLock(CCriticalSection cs); 12 ~CLock(); 13 }; 14 #endif 15 16 //Lock.cpp文件 17 #include "Lock.h" 18 CLock::CLock(CCriticalSection cs) :m_cs(cs) 19 { 20 m_cs.Lock(); 21 } 22 CLock::~CLock() 23 { 24 m_cs.Unlock(); 25 }
Singleton v3.0代码如下:
1 //Singleton.h 2 #pragma once 3 #ifndef SINGLETON_H 4 #define SINGLETON_H 5 #include<iostream> 6 using namespace std; 7 /* 8 * author:caoshuping 9 * date:2016.03.14 10 * 说明:单例模式在C++中的实现 11 * 实现步骤: 12 * 1.将构造方法私有化,防止在类外进行实例化。 13 * 2.声明静态指针,指针指向Singleton,也就是指向唯一可以访问对象的资源实例的指针 14 * 3.提供唯一的获得实例的结构GetInstance() 15 * 类型:饿汉模式 and 懒汉模式 16 */ 17 class Singleton 18 { 19 private: 20 Singleton(); 21 22 //垃圾回收类,这个类主要功能是在其析构函数中释放m_pInstance所占用的资源 23 class CGarbo 24 { 25 public : 26 //在析构函数中释放m_pInstance所占用的资源 27 ~CGarbo() 28 { 29 if (m_pInstance != NULL) 30 delete m_pInstance; 31 }; 32 }; 33 //在Singleton中定义CGarbo的静态变量,这样在程序结束的时候程序会销毁程序中使用的静态变量, 34 //在销毁garbo对象的时候执行CGarbo类的析构函数,从而实现了对m_pInstance的释放 35 static CGarbo garbo; 36 static Singleton *m_pInstance; 37 //定义CCriticalSection对象,也就是锁对象 38 static CCriticalSection cs; 39 public: 40 static Singleton * GetInstance(); 41 ~Singleton(); 42 }; 43 #endif 44 45 //Singleton.cpp 46 #include "Singleton.h" 47 #include <iostream> 48 #include "Lock.h" 49 using namespace std; 50 51 //类中的静态成员变量必须在类外进行初始化,不然会报link error 52 Singleton *Singleton::m_pInstance = NULL; 53 //饿汉模式。也就是在类加载时候就对静态成员变量进行初始化。 54 //Singleton *Singleton::m_pInstance = new Singleton(); 55 56 Singleton::Singleton() 57 { 58 m_pInstance = NULL; 59 } 60 61 62 Singleton::~Singleton() 63 { 64 } 65 66 /* 67 * 单例模式的唯一接口,使外部可以获得唯一的指向资源的对象实例。 68 */ 69 Singleton *Singleton::GetInstance() 70 { 71 //判断是否为空,为空的话进行初始化 72 //这种模式的单例模式为懒汉模式。也就是只有第一次需要使用这个对象实例的时候才对对象进行初始化 73 //当对静态成员变量m_pInstance在类外直接调用new Singleton()进行对象初始化。这种为饿汉模式。可以看第8,9行代码 74 //双重判断,如果不加第一层的if判断,则每次在调用GetInstance时都要建立CLock对象,这样会降低效率,而加了则可以避免。 75 if (m_pInstance == NULL) 76 { 77 //建立锁CLock对象 78 CLock lock(cs); 79 //临界区 80 if (m_pInstance == NULL) 81 m_pInstance = new Singleton(); 82 //临界区 83 84 //注意解锁的情况,由于解锁lock是局部变量,所以在函数结束后会自动销毁,在自动销毁的时候会执行析构函数进行解锁 85 } 86 87 88 return m_pInstance; 89 }
以上是关于设计模式之单例模式的主要内容,如果未能解决你的问题,请参考以下文章