浅谈C++设计模式--单例模式
单例模式(Singleton)[分类:创建型] [目的:为程序的某个类的访问,提供唯一对象实例]
这估计是设计模式里面最简单的一个类了,下面我们一起看看,单例它是什么?以及如何实现一个单例
基本定义
保证一个类仅有一个实例,并提供一个访问它的全局访问点.
个人理解
就是一个类在整个程序里面,有且仅有一个实例,这个实例由该类自己负责创建和保存,这样保证它不会在任何其他地方被创建,必须提供一个访问这个实例的全局访问接口.
那么有些人可能有疑问,为什么不直接用一个全局变量来控制外部对它的访问呢?在这里如果用全局变量来控制外部的访问它实际上还是不能避免你实例化多个对象,这样还是达不到单例有且仅有一个实例的目的。
单例的特征
有且仅有一个实例
实例必须由自己创建和保存
要提供和给外部访问实例的接口
UML结构图
从UML结构图中我们可以一步步实现出以下代码
简单版本的单例模式
1 class Singleton 2 { 3 public: 4 static Singleton& GetInstance() 5 { 6 static Singleton instance; 7 return instance; 8 } 9 private:10 Singleton();11 12 };
这里我们可以看到,这个版本构造函数被私有化了,因此外部无法直接调用单例的构造器,而且使用静态的局部变量做返回,这样不需要考虑对内存的管理,是不是非常方便!但是我们在对函数执行以下操作时
1 Singleton instance1=Singleton::GetInstance(); 2 Singleton instance2=Singleton::GetInstance();3 qDebug()<<"instance1 address:"<<&instance1;4 qDebug()<<"instance2 address:"<<&instance2;
我们运行程序看结果
在这里instance1和instance2实例他们的地址是完全不同的.这样的做法是不可取的,不是真正的单例,那么如何避免这样的问题的?跟开发者说你们直接调用GetInstance接口就行。
NONONO....
下面我们看看修改版本的单例看看
1 class SingletonModify 2 { 3 public: 4 static SingletonModify& GetInstance() 5 { 6 static SingletonModify instance; 7 return instance; 8 } 9 void sayHello();10 private:11 SingletonModify();12 SingletonModify(SingletonModify const &);//to use this modify wrong demo13 SingletonModify& operator =(SingletonModify const &);//to use this modify wrong demo14 };
从上面我们可以看出,我们重新定义了构造器以及=操作,这里不需要实现。然后我们再用相同的方式测试看看,这下编译器编译不通过了,那我们想调用实例的方法或者操作实例怎么办呢?
SingletonModify::GetInstance().sayHello();//after modify code,
那我们只能通过上述方法访问实例了,保证了实例的唯一性了。
简单版本的单例模式技术总结
将构造器私有化(保证外部无法创建实例)
使用静态的局部变量作为返回(无需考虑内存管理的问题)
重新定义赋值运算符以及构造器.(避免=发生拷贝操作)
是不是觉得到这里单例就已经差不多啦?并不是,我们使用的是C++,但是并没有使用到C++的一个重要特征:指针
首先我们针对上述简单版本的单例模式再次进行修正,我们修改单例的函数返回,用指针接收,看看下面代码
1 class SingletonSimplify 2 { 3 public: 4 SingletonSimplify(); 5 static SingletonSimplify* GetInstanceptr()//simplify method 6 { 7 static SingletonSimplify instance; 8 return &instance; 9 }10 };
测试:
1 SingletonSimplify* instanceSmp1=SingletonSimplify::GetInstanceptr();//another simplify singleton method2 SingletonSimplify* instanceSmp2=SingletonSimplify::GetInstanceptr();3 qDebug()<<"instanceSmp1 address:"<<instanceSmp1;4 qDebug()<<"instanceSmp2 address:"<<instanceSmp2;
我们可以发现,用指针接收之后2个指针都是指向同一个地址,很好,这也是一种单例的实现方式,但是这个也属于访问静态局部对象,还是没有真正意义上使用到指针.再看看下面两种方式
懒汉模式
1 SingletonLazyMode *SingletonLazyMode::mSingletonInstance=NULL; 2 SingletonLazyMode *SingletonLazyMode::GetInstance() 3 { 4 if(mSingletonInstance==NULL) 5 { 6 mSingletonInstance=new SingletonLazyMode(); 7 mSingletonInstance->num=20; 8 } 9 return mSingletonInstance;10 }11 void SingletonLazyMode::InstanceDispose()12 {13 if(mSingletonInstance!=NULL)14 {15 delete mSingletonInstance;16 mSingletonInstance=NULL;17 }18 }19 void SingletonLazyMode::sayHi()20 {21 qDebug()<<"hi lazy man! Number:"<<num;22 }23 SingletonLazyMode::SingletonLazyMode()24 {25 }
1 class SingletonLazyMode 2 { 3 public: 4 static SingletonLazyMode* GetInstance(); 5 static void InstanceDispose(); 6 void sayHi(); 7 private: 8 SingletonLazyMode(); 9 static SingletonLazyMode* mSingletonInstance;10 int num=10;11 };
先看头文件.h代码
.cpp实现
饿汉模式
1 SingletonEagerMode *SingletonEagerMode::mEagerInstance=new SingletonEagerMode(); 2 SingletonEagerMode *SingletonEagerMode::GetInstance() 3 { 4 return mEagerInstance; 5 } 6 void SingletonEagerMode::InstanceDispose() 7 { 8 if(mEagerInstance!=NULL) 9 {10 delete mEagerInstance;11 mEagerInstance=NULL;12 }13 }14 SingletonEagerMode::SingletonEagerMode()15 {1617 }
1 class SingletonEagerMode2 {3 public:4 static SingletonEagerMode* GetInstance();5 static void InstanceDispose();6 private:7 SingletonEagerMode();8 static SingletonEagerMode* mEagerInstance;9 };
先看头文件.h代码
.cpp实现
测试代码
1 SingletonLazyMode* lazyinstance1=SingletonLazyMode::GetInstance();//lazy mode 懒汉模式 2 SingletonLazyMode* lazyinstance2=SingletonLazyMode::GetInstance(); 3 lazyinstance1->sayHi(); 4 lazyinstance2->sayHi(); 5 qDebug()<<"lazyinstance1 address:"<<lazyinstance1; 6 qDebug()<<"lazyinstance2 address:"<<lazyinstance2; 7 SingletonLazyMode::InstanceDispose(); 8 qDebug()<<"lazyinstance1 address:"<<lazyinstance1; 9 qDebug()<<"lazyinstance2 address:"<<lazyinstance2;10 lazyinstance1->sayHi();11 lazyinstance2->sayHi();12 SingletonEagerMode* eagerinstance1=SingletonEagerMode::GetInstance();//eager mode 饿汉模式13 SingletonEagerMode* eagerinstance2=SingletonEagerMode::GetInstance();14 qDebug()<<"eagerinstance1 address:"<<eagerinstance1;15 qDebug()<<"eagerinstance2 address:"<<eagerinstance2;
运行效果:
这里我们可以看到不管是懒汉还是饿汉模式两个实例的地址均是相同的。说明我们的单例是OK的
技术总结:
下面对懒汉模式和饿汉模式经行对比分析其异同点:
懒汉模式初始化对象是在程序调用的时候,非线程安全,由于最终实现要加Qmutex进行枷锁处理,执行效率会相对而言要低
饿汉模式是程序启动的时候就已经创建好了,浪费内存,但属于线程安全,执行效率相对懒汉而言要高
懒汉/饿汉模式实现结构基本类似
相同点:
不同点:
问题点:
细心的同学可能发现了,在上面测试过程中我调用了自己定义的Dispose接口,但是还是能再次调用lazyinstance1,lazyinstance2实例中的函数和变量???这是在MinGW编译器下执行的结果,当我将编译器换成MSVC时,显示内存已经被释放掉了
下图MSVC下的执行结果
-
参考了一篇博客也没看出什么问题:https://www.cnblogs.com/chengjundu/p/11283123.html
如果有朋友知道望不吝赐教!!!!感谢。
最后看看我们线程安全的懒汉实现方式
.h文件
1 class SingletonThreadSafety 2 { 3 public: 4 static SingletonThreadSafety* GetInstance(); 5 static void InstanceDispose(); 6 private: 7 SingletonThreadSafety(); 8 static SingletonThreadSafety* mSaftyInstance; 9 static QMutex mMutex;10 };
.cpp代码
1 SingletonThreadSafety *SingletonThreadSafety::mSaftyInstance=NULL; 2 QMutex SingletonThreadSafety::mMutex; 3 SingletonThreadSafety *SingletonThreadSafety::GetInstance() 4 { 5 if(mSaftyInstance==NULL) 6 { 7 QMutexLocker locker(&mMutex); 8 if(mSaftyInstance==NULL) 9 {10 mSaftyInstance=new SingletonThreadSafety();11 }12 }13 return mSaftyInstance;14 }15 void SingletonThreadSafety::InstanceDispose()16 {17 if(mSaftyInstance!=NULL)18 {19 delete mSaftyInstance;20 mSaftyInstance=NULL;21 }22 }23 SingletonThreadSafety::SingletonThreadSafety()24 {25 26 }
这就完美解决掉了线程安全问题,但是在获取实例对象的时候需要对Qmutex进行判断,这会损失一点点性能。
以上就是对单例模式的完整概述
下面进行全面的技术总结:
在写单例模式的时候我们要考虑到以下几个方面:
要封闭默认的构造函数,以防止多地方创建对象
类提供一个静态的对象,用来保存该实例
提供一个公共的访问实例的接口GetInstance
考虑线程安全问题
以上单例所有内容,如有错误请指出!!!
参考<<大话设计模式>>一书
附源代码:
https://gitee.com/xiaochunlu/designer-pattern/tree/master/Singleton_Pattern