单例模式,相信大家也并不陌生,而且在项目中也会经常用到,对于频繁使用的类,我们都想要提高它的复用性,今天小豆君就给大家介绍一下如何用类模板来实现一个单例模板类。
友情提示,本篇文章会有点长,如果你真想学到点东西,那就仔细阅读吧,而且有很多细节都是非常棒的编程技巧,相信你会学到不少好东西的。
1 单例类实现
1.1 本地单例类实现方法
//该类将构造函数,拷贝构造函数,赋值构造函数访问权限设置成private,//这样就屏蔽了直接创建实例的操作,而想要获得该类的实例,只能通过//instance方法获取class LocalStaticInstanceInt {public: static LocalStaticInstanceInt& intance() { //此处声明为局部静态变量,只在第一次调用时创建并初始化 static LocalStaticInstanceInt instance; return instance; } virtual ~LocalStaticInstanceInt() { cout << "destory LocalStaticInstanceInt" << endl; } int getValue() const{return value;} void setValue(int val){value = val;}private: LocalStaticInstanceInt():value(0) //防止实例 { cout << "create LocalStaticInstanceInt" << endl; } LocalStaticInstanceInt(const LocalStaticInstanceInt&) {} //防止拷贝构造一个实例 LocalStaticInstanceInt& operator=(const LocalStaticInstanceInt&){return *this;} //防止赋值出另一个实例private: int value; };
1.2 懒惰初始化单例类实现方法
有的时候,采用单例的指针来访问类方法更方便点
class LazyInstance {public: static LazyInstance* intance() { static LazyInstance* ins = new LazyInstance(); return ins; } virtual ~LazyInstance() { cout << "destory LazyInstance" << endl; } int getValue() const{return value;} void setValue(int val){value = val;}private: LazyInstance():value(0) //防止实例 { cout << "create LazyInstance" << endl; } LazyInstance(const LazyInstance&) {} //防止拷贝构造一个实例 LazyInstance& operator=(const LazyInstance&){return *this;} //防止赋值出另一个实例 int value; };
因为只有在调用单例方法时才会创建一个实例,所以这种技术叫做懒惰初始化法,当然1.1中的方法也运用了懒惰初始化法。
1.3 测试
int main(){ { LocalStaticInstanceInt& lsi1 = LocalStaticInstanceInt::intance(); LocalStaticInstanceInt& lsi2 = LocalStaticInstanceInt::intance(); cout << "ins1.value = "<< lsi1.getValue() << " ins2.value = " << lsi2.getValue() << endl; //因为lsi1与lsi2是同一个对象的引用,所以当lsi1设置value为5时,lsi2的也相应改变 lsi1.setValue(5); cout << "ins1.value = "<< lsi1.getValue() << " ins2.value = " << lsi2.getValue() << endl; } { LazyInstance* si1 = LazyInstance::intance(); LazyInstance* si2 = LazyInstance::intance(); cout << "ins1.value = "<< si1->getValue() << " ins2.value = " << si2->getValue() << endl; si1->setValue(5); cout << "ins1.value = "<< si1->getValue() << " ins2.value = " << si2->getValue() << endl; } }
以下是输出结果:
image.png
细心的同学可能发现,在输出结果中,只释放了LocalStaticInstance的实例,而LazyInstance的实例并未释放,这是因为,LazyInstance的实例是在堆上创建的,而在程序结束时,并没有对该实例进行释放操作,所以就造成了内存泄漏。
1.4使用atexist函数释放实例对象
针对LazyInstance实例没有释放的问题,可以采用atexist注册一个函数,让程序退出时释放LazyInstance实例,当然你也可以做一些其它的收尾操作。
//在main函数之上添加如下函数void destroyInstance(void){ LazyInstance* si = LazyInstance::intance(); if (si) { delete si; } }//在main函数内最后一行添加调用atexit(destroyInstance);
再次运行程序
image.png
这回LazyInstance已经被释放了。
1.5 更好的办法
使用atexist固然可以解决问题,但每次都需要向destroyInstance中添加删除操作,无疑是麻烦的,而且有的人也会忘记添加。所以又有了下面的对策。
template <class T>class Destroyer{ T* doomed;public: Destroyer(T* q) : doomed(q) { assert(doomed); } ~Destroyer(); };template <class T>Destroyer<T>::~Destroyer() { try { if(doomed) delete doomed; } catch(...) { } doomed = 0; }class LazyInstance {public: static LazyInstance* intance() { static LazyInstance* ins = new LazyInstance(); //新增一个局部静态变量destory,当程序结束时, //会调用该变量的析构函数,从而用这个析构函数释放LazyInstance实例 static Destroyer<LazyInstance> destory(ins); return ins; } virtual ~LazyInstance() { cout << "destory LazyInstance" << endl; } int getValue() const{return value;} void setValue(int val){value = val;}private: LazyInstance():value(0) //防止实例 { cout << "create LazyInstance" << endl; } LazyInstance(const LazyInstance&) {} //防止拷贝构造一个实例 LazyInstance& operator=(const LazyInstance&){return *this;} //防止赋值出另一个实例 int value; };
在代码中,我定义了一个Destroyer的类模板,并且在instance()接口中新定义了一个Destroyer<LazyInstance>类型的局部静态变量,并且以LazyInstance的实例指针作为参数,那么当程序结束时,会自动调用Destroyer<LazyInstance>的析构函数,而在析构函数中,释放了LazyInstance实例。
修改main函数
int main() { LazyInstance* si1 = LazyInstance::intance(); LazyInstance* si2 = LazyInstance::intance(); cout << "ins1.value = "<< si1->getValue() << " ins2.value = " << si2->getValue() << endl; si1->setValue(5); cout << "ins1.value = "<< si1->getValue() << " ins2.value = " << si2->getValue() << endl; }
运行程序
image.png
LazyInstance的实例被自动释放。
2 重用单例类
在第1小节中,我们创建了一个带有引用的单例LocalStaticInstanceInt,该单例对一个int型的成员变量进行了get和set操作,假设现在又有一个类需要用到单例模式,那么通常的做法是,再创建一个单例类。
例如以下单例,操作一个string对象:
class LocalStaticInstanceString {public: static LocalStaticInstanceString& intance() { static LocalStaticInstanceString instance; return instance; } virtual ~LocalStaticInstanceString() { cout << "destory LocalStaticInstance" << endl; } string getValue() const{return value;} void setValue(string val){value = val;}private: LocalStaticInstanceString():value(0) //防止实例 { cout << "create LocalStaticInstance" << endl; } LocalStaticInstanceString(const LocalStaticInstanceString&) {} //防止拷贝构造一个实例 LocalStaticInstanceString& operator=(const LocalStaticInstanceString&){return *this;} //防止赋值出另一个实例private: string value; };
LocalStaticInstanceString与LocalStaticInstanceInt长得如此相像,为了提高重用性,那么我们可以采用继承的方法重写这两个类,而基类采用单例类模板
template <class T>class LocalStaticInstance {public: static T& intance() { static T sinstance; return sinstance; } virtual ~LocalStaticInstance(){}//此处使用protected是为了派生类可以继承于它,并调用默认构造函数//如果是private则无法调用protected: LocalStaticInstance() = default; //防止实例 LocalStaticInstance(const LocalStaticInstance&) {} //防止拷贝构造一个实例 LocalStaticInstance& operator=(const LocalStaticInstance&){return *this;} //防止赋值出另一个实例};//继承LocalStaticInstance<LocalStaticInstanceInt>类,//单例的instance的方法就不需要你写了class LocalStaticInstanceInt: public LocalStaticInstance<LocalStaticInstanceInt> {public: int getValue() const {return value;} void setValue(int val) {value = val;} //使用友元,使基类可以调用私有的构造函数 friend class LocalStaticInstance<LocalStaticInstanceInt>;private: LocalStaticInstanceInt() { cout << "create LocalStaticInstanceInt" << endl; } virtual ~LocalStaticInstanceInt() { cout << "destory LocalStaticInstanceInt" << endl; }private: int value; };//继承LocalStaticInstance<LocalStaticInstanceString>类class LocalStaticInstanceString: public LocalStaticInstance<LocalStaticInstanceString> {public: string getValue() const {return value;} void setValue(string val) {value = val;} friend class LocalStaticInstance<LocalStaticInstanceString>;private: LocalStaticInstanceString():value("hello") { cout << "create LocalStaticInstanceString" << endl; } ~LocalStaticInstanceString() { cout << "destory LocalStaticInstanceString" << endl; }private: string value; };
下面的main函数展示了如何调用
int main(){ { LocalStaticInstanceInt& lsi1 = LocalStaticInstance<LocalStaticInstanceInt>::intance(); LocalStaticInstanceInt& lsi2 = LocalStaticInstance<LocalStaticInstanceInt>::intance(); cout << "lsi1.value = "<< lsi1.getValue() << " lsi2.value = " << lsi2.getValue() << endl; lsi1.setValue(5); cout << "lsi1.value = "<< lsi1.getValue() << " lsi2.value = " << lsi2.getValue() << endl; } { LocalStaticInstanceString& lss1 = LocalStaticInstanceString::intance(); LocalStaticInstanceString& lss2 = LocalStaticInstanceString::intance(); cout << "lss1.value = "<< lss1.getValue() << " lss2.value = " << lss2.getValue() << endl; lss1.setValue("world"); cout << "lss1.value = "<< lss1.getValue() << " lss2.value = " << lss2.getValue() << endl; } }
通过上面的分析,使用类模板减少了很多重复的代码,在使用过程中,直接继承LocalStaticInstance的模板类即可,是不是用起来方便多了。
对于LazyInstance(使用指针实现的懒惰初始化单例类)的类模板实现,请大家作为练习。
3 合并多个单例基类类模板
上一小节我们创建了两个单例基类模板LocalStaticInstance和LazyInstance(作为练习)
我们在使用中,如果两种模板都想用,那么,使用起来还是会不太方便。因此,有没有一种方法能够把这两个类合并起来呢,答案当然是有的,我们可以创建一个新模板,然后把这两个类作为参数传入新模板的接口中,到时候如果想使用指针,则可以把LazyInstance类型作为参数传入模板,就像我们使用vector时传入容器大小一样方便。
#include <iostream>#include <assert.h>using namespace std;//使用局部静态变量实现的单例类//注意这是类,而不是模板,它实现了下面Singleton模板的create方法class LocalStaticInstance {protected: template <class T> static void create(T*& ptr) { static T instance; ptr = &instance; } };//释放器 用于释放在堆上分配的单例template <class T>class Destroyer{ T* doomed;public: Destroyer(T* q) : doomed(q) { assert(doomed); } ~Destroyer(); };//在类外实现的析构函数,注意析构函数开头要写成Destroyer<T>形式//因为Destroyer<T>才是一个真正的类型template <class T>Destroyer<T>::~Destroyer() { try { if(doomed) delete doomed; } catch(...) { } doomed = 0; }//使用指针实现,懒惰初始化单例类class LazyInstance{protected: template <class T> static void create(T*& ptr) { ptr = new T; static Destroyer<T> destroyer(ptr); } };//Singleton类对外提供一致接口,默认调用LazyInstancetemplate <class T, class InstancePolicy=LazyInstance>class Singleton : private InstancePolicy {public: static T* instance(); };//在类声明之外定义instance接口template <class T, class InstancePolicy>T* Singleton<T, InstancePolicy>::instance() { static T* ptr = 0; if(!ptr) { InstancePolicy::create(ptr); } return const_cast<T*>(ptr); }//使用局部静态单例类,需要显式指定class LocalStaticInstanceInt: public Singleton<LocalStaticInstanceInt, LocalStaticInstance> {public: virtual ~LocalStaticInstanceInt() { cout << "destory LocalStaticInstanceInt" << endl; } int getValue() const {return value;} void setValue(int val) {value = val;} friend class LocalStaticInstance;private: LocalStaticInstanceInt():value(0) { cout << "create LocalStaticInstanceInt" << endl; } int value; };//使用懒惰初始化单例类 默认class LocalStaticInstanceString: public Singleton<LocalStaticInstanceString> {public: ~LocalStaticInstanceString() { cout << "destory LocalStaticInstanceString" << endl; } string getValue() const {return value;} void setValue(string val) {value = val;} friend class LazyInstance;private: LocalStaticInstanceString():value("hello") { cout << "create LocalStaticInstanceString" << endl; } string value; };
LocalStaticInstance和LazyInstance实现了create方法,用于创建单例,而在Singleton的instance的方法中调用create方法
main函数调用方法
int main() { { LocalStaticInstanceInt* lsi1 = Singleton<LocalStaticInstanceInt>::instance(); LocalStaticInstanceInt* lsi2 = Singleton<LocalStaticInstanceInt>::instance(); cout << "lsi1.value = "<< lsi1->getValue() << " lsi2.value = " << lsi2->getValue() << endl; lsi1->setValue(5); cout << "lsi1.value = "<< lsi1->getValue() << " lsi2.value = " << lsi2->getValue() << endl; } { LocalStaticInstanceString* lss1 = Singleton<LocalStaticInstanceString>::instance(); LocalStaticInstanceString* lss2 = Singleton<LocalStaticInstanceString>::instance(); cout << "lsi1.value = "<< lss1->getValue() << " lsi2.value = " << lss2->getValue() << endl; lss1->setValue("world"); cout << "lsi1.value = "<< lss1->getValue() << " lsi2.value = " << lss2->getValue() << endl; } }
输出
image.png
输出完美,现在的代码非常简洁,是不是很神奇啊。
我们继续往下探索
5 线程安全
因为我们使用的是单例,全程序只有一个实例,如果有多个线程修改这个单例,就有可能出现多个线程同时修改的情况,那么这就是线程不安全的,那么我们很有必要对公共资源的同步控制,那么最简单的方法就是加一把锁。
因为我们都是学Qt的,那么小豆君就给它的参数默认为QMutex
//Singleton类对外提供一致接口,默认调用LazyInstancetemplate <class T, class InstancePolicy=LazyInstance, class LockType = QMutex>class Singleton : private InstancePolicy {public: static T* instance(); };//在类声明之外定义instance接口template <class T, class InstancePolicy, class LockType>T* Singleton<T, InstancePolicy, LockType>::instance() { static T* ptr = 0; static LockType lock; if(!ptr) { QMutexLocker ml(&lock); if(!ptr) InstancePolicy::create(ptr); } return const_cast<T*>(ptr); }
6 禁用复制构造函数
要达到一个完整的单例,还需要禁用拷贝构造函数和赋值构造函数,为此我们可以创建一个基类A,将A的拷贝构造函数和赋值构造函数声明设为私有,默认构造函数声明为受保护的,让Singleton<>私有继承A。那么凡是由Singleton<>派生的类就都被禁用了拷贝构造函数和赋值构造函数。因为A的拷贝构造和赋值构造在Singleton<>中都已经变成私有的了,当然Singleton<>的派生类就不可能在调用复制构造函数了。
//禁止复制基类class NonCopyable{ NonCopyable(const NonCopyable&); const NonCopyable& operator=(const NonCopyable&);protected: NonCopyable() {} ~NonCopyable() {} };//Singleton类对外提供一致接口,默认调用LazyInstance//私有继承NonCopyable,禁止复制template <class T, class InstancePolicy=LazyInstance, class LockType = QMutex>class Singleton : private InstancePolicy,private NonCopyable {public: static T* instance(); };//在类声明之外定义instance接口template <class T, class InstancePolicy, class LockType>T* Singleton<T, InstancePolicy, LockType>::instance() { static T* ptr = 0; static LockType lock; if(!ptr) { QMutexLocker ml(&lock); if(!ptr) InstancePolicy::create(ptr); } return const_cast<T*>(ptr); }
7 完整的单例类模板
在做了这么多探索和分析之后,我们终于可以看看最终的代码了。
noncopyable.h
#ifndef NONCOPYABLE_H#define NONCOPYABLE_Hclass NonCopyable{ NonCopyable(const NonCopyable&); const NonCopyable& operator=(const NonCopyable&);protected: NonCopyable() {} ~NonCopyable() {} };#endif // NONCOPYABLE_H
singleton.h
#ifndef SINGLETON_H#define SINGLETON_H#include <QMutexLocker>#include <cassert>#include "noncopyable.h"http:///--start 局部静态变量单例类实现//注意这是类,而不是模板,它实现了下面Singleton模板的create方法class LocalStaticInstance {protected: template <class T> static void create(T*& ptr) { static T instance; ptr = &instance; } };///--end///--start 懒惰初始化单例类实现//释放器 用于释放在堆上分配的单例template <class T>class Destroyer{ T* doomed;public: Destroyer(T* q) : doomed(q) { assert(doomed); } ~Destroyer(); };//在类外实现的析构函数,注意析构函数开头要写成Destroyer<T>形式//因为Destroyer<T>才是一个真正的类型template <class T>Destroyer<T>::~Destroyer() { try { if(doomed) delete doomed; } catch(...) { } doomed = 0; }class LazyInstance{protected: template <class T> static void create(T*& ptr) { ptr = new T; static Destroyer<T> destroyer(ptr); } };///--end///--start Singleton类实现//对外提供一致接口,默认调用LazyInstance,QMutex,你也可以换成其它库的锁template <class T, class InstancePolicy=LazyInstance, class LockType = QMutex>class Singleton : private InstancePolicy,private NonCopyable {protected: Singleton(){}public: static T* instance(); };//在类声明之外定义instance接口template <class T, class InstancePolicy, class LockType>T* Singleton<T, InstancePolicy, LockType>::instance() { static T* ptr = 0; static LockType lock; if(!ptr) { QMutexLocker ml(&lock); if(!ptr) { InstancePolicy::create(ptr); } } return const_cast<T*>(ptr); }///--end#endif // SINGLETON_H
好了,关于单例类模板的详细介绍终于写完了,相信大家也学到了不少好东西,最后不要忘了给小豆君点个赞哦。
作者:小豆君的干货铺
链接:https://www.jianshu.com/p/e641119805f5
共同學習,寫下你的評論
評論加載中...
作者其他優質文章