二、使用辅助类实现按需释放的单例模式
例子工程的名称是SingleClassFreeInstanceGetter。
1、引入辅助类
为什么会想到引入辅助类来实现单例,这个辅助类要实现什么样的功能来辅助,要回答这两个问题,我们还是要先分析前文中要求单例类具有的特点,简单来讲就是2个特点,第一是要单例,第二是要具有生命周期,不能创建了就不管,关于单例研究的已经很多了,没有多少进一步处理的空间了,如果非要进一步处理,结果可能就是单例本身太复杂不实用,我们来看生命周期,单例类自己控制生命周期是不可以的,因为不知道什么时候来释放和创建,变成普通对象可以控制生命周期,但是单例问题解决不了,所以我们要使用辅助类来控制单例类的生命周期,关于控制其他类的生命周期大家可以想想,我们知道的类中什么类是控制其他对象生命周期的,如果熟悉STL等模板库,一定知道auto_ptr是控制其他类的生命周期的,所以我们可以模仿auto_ptr建立辅助类来控制单例类的生命周期,功能上不需要像auto_ptr那么复杂全面,只要实现以下2个功能就可以
A、一个GetIntance方法获取单例类;
B、控制好单例类的生命周期,当这个辅助类第一次创建时创建单例类,以后再创建辅助类就直接引用单例类实例,增加单例类引用次数(注意这里不是在GetIntance的时候增加引用次数),当辅助类释放时,减少单例类的引用次数,当引用次数为0时,表明单例类不再被使用了,就释放单例类。
基于这样的分析,我们假设我们创建了一个单例类SingletonExample和其单例实例获取器辅助类SingletonExampleFreeInstanceGetter,那么就可以使用如下的代码来获得单例类的实例
SingletonExampleFreeInstanceGetter sigSingletonInstanceGetter1;
SingletonExample *pseSingleton1 = sigSingletonInstanceGetter1.GetInstance();
i = pseSingleton1->GetVariable();
SingletonExampleFreeInstanceGetter sigSingletonInstanceGetter2;
SingletonExample *pseSingleton2 = sigSingletonInstanceGetter2.GetInstance();
i = pseSingleton2->GetVariable();
以上代码中,先定义单例类获取器SingletonExampleFreeInstanceGetter的对象实例,然后通过其实例成员方法GetInstance获取单例类SingletonExample的实例,这里单例类SingletonExample实例的指针pseSingleton1和pseSingleton2指向了同一个SingletonExample的实例,即具有了单例的效果。
继续看看生命周期,这个单例实例创建并增加引用计数的时间应该发生在
SingletonExampleFreeInstanceGetter sigSingletonInstanceGetter1;
这样实现了延迟创建,当单例获取器的2个对象实例sigSingletonInstanceGetter1和sigSingletonInstanceGetter2都被释放的时候,这个单例类SingletonExample的实例也会被释放,即可以通过辅助类的生命周期控制单例类的按需释放。
2、单例类实例获取器
基于以上的分析,我们就可以实现单例类实例获取器
类定义
//单例类的实例获得器
class SingletonExampleFreeInstanceGetter
{
public:
SingletonExampleFreeInstanceGetter(void);
~SingletonExampleFreeInstanceGetter(void);
public:
SingletonExample *GetInstance();
private:
static SingletonExample *m_pInstance;
static unsigned int m_nReference;
};
类实现
//SingletonExampleInstanceGetter类实现
SingletonExample *SingletonExampleFreeInstanceGetter::m_pInstance = NULL;
unsigned int SingletonExampleFreeInstanceGetter::m_nReference = 0;
SingletonExampleFreeInstanceGetter::SingletonExampleFreeInstanceGetter(void)
{
if (m_pInstance == NULL)
{
try
{
m_pInstance = new SingletonExample();
}
catch (...) //防止new分配内存可能出错的问题,如果是内存分配错误异常为std::bad_alloc
{
m_pInstance = NULL;
}
}
//这里不管SingletonExample创建成功与否都要加1
m_nReference++;
}
SingletonExampleFreeInstanceGetter::~SingletonExampleFreeInstanceGetter(void)
{
m_nReference--;
if (m_nReference == 0)
{
if (m_pInstance != NULL)
{
delete m_pInstance;
m_pInstance = NULL; //非常重要,不然下次再次建立单例的对象的时候错误
}
}
}
SingletonExample* SingletonExampleFreeInstanceGetter::GetInstance()
{
return m_pInstance;
}
这个类提供了一个GetInstance方法,返回单例类SingletonExample的实例指针,为了保存这个单例类,定义了一个静态成员保存单例类的指针,并定义了一个静态成员m_nReference保存单例类被引用的次数,当定义SingletonExampleFreeInstanceGetter对象实例时,SingletonExampleFreeInstanceGetter的构造方法就检查m_pInstance,如果单例类不存在就创建单例类的实例,并增加访问计数m_nReference,当SingletonExampleFreeInstanceGetter对象实例被释放时,首先减少访问计数m_nReference,然后检查m_nReference,如果计数为0,就说明单例类不再被使用,就释放单例类实例。
从这里可以看出这个单例类实例获取器并不复杂,并且能够实现前文中提到的需要具备的2个功能了。
3、单例类
现在来看看单例类
单例类的定义
//单例类
class SingletonExample
{
public:
SingletonExample(void);
~SingletonExample(void);
public:
int GetVariable();
void SetVariable(const int nValue);
private:
int m_nVariable;
};
看到这个类定义以后读者不要感到吃惊,没错,就是一个普通类,也就是说当有了上面的辅助类以后,如果开发人员不创建这个类的实例,而是通过上面的辅助类来获取实例,任何类都是单例类,而且生存周期都是被单例类实例获取器的实例控制的,单例类根本不需要什么额外的开发就可以成为单例类,不需要定义什么静态成员,不需要实现什么GetInstance方法。
当然读者肯定觉得这样不好,如何控制不让开发人员自己创建这个类的实例,必须通过辅助类来获取呢,这个要办到也很简单,只需要对这个普通类修改补充2点
A、将要成为单例类的SingletonExample的构造函数变成private,不允许开发人员自己创建这个类;
B、使用友元对单例类实例获取器开放,只让单例类实例获取器能访问这个类的构造函数即可。
下面是修改过的单例类定义
//单例类
class SingletonExample
{
friend class SingletonExampleFreeInstanceGetter;
private:
SingletonExample(void);
public:
~SingletonExample(void);
public:
int GetVariable();
void SetVariable(const int nValue);
private:
int m_nVariable;
};
类实现
#include "SingletonExample.h"
//SingletonExample类实现
SingletonExample::SingletonExample(void)
{
m_nVariable = 0;
}
SingletonExample::~SingletonExample(void)
{
}
int SingletonExample::GetVariable()
{
return m_nVariable;
}
void SingletonExample::SetVariable(const int nValue)
{
m_nVariable = nValue;
}
只需要类定义时把构造函数变成private的
private:
SingletonExample(void);
然后使用友元让实例类获取器类能访问本类的构造函数
friend class SingletonExampleFreeInstanceGetter;
这样单例类实例获取器类SingletonExampleFreeInstanceGetter可以使用这个类的构造函数创建此类的实例,而开发人员手工是不可以创建这个类的实例的。
此处使用了友元类,因为本类的私有构造函数是在单例类实例获取器SingletonExampleFreeInstanceGetter的构造函数中调用的,所以也可以使用友元函数只向单例类实例获取器SingletonExampleFreeInstanceGetter的构造函数开放访问权限,来达到同样的效果,本系列文章的后续内容是就是使用的友元函数,这样能更好的避免单例类实例获取器对于实例类其他成员的可能破坏干扰。
4、测试程序例子及结果
测试程序的代码如下
int _tmain(int argc, _TCHAR* argv[])
{
int i;
{
SingletonExampleFreeInstanceGetter sigSingletonInstanceGetter1;
SingletonExample *pseSingleton1 = sigSingletonInstanceGetter1.GetInstance();
i = pseSingleton1->GetVariable();
cout << "实例成员变量初始值:" << i << endl;
pseSingleton1->SetVariable(10);
i = pseSingleton1->GetVariable();
cout << "改变实例成员变量以后的值:" << i << endl;
{
SingletonExampleFreeInstanceGetter sigSingletonInstanceGetter2;
SingletonExample *pseSingleton2 = sigSingletonInstanceGetter2.GetInstance();
i = pseSingleton2->GetVariable();
cout << "不释放前一实例获取器的情况下使用新实例获取器获得的实例成员变量的值:" <<
i << endl;
SingletonExample *pseSingleton3 = sigSingletonInstanceGetter2.GetInstance();
i = pseSingleton3->GetVariable();
cout << "不释放前一实例获取器的情况下使用新实例获取器再次获得的实例成员变量的值:" <<
i << endl;
}
}
SingletonExampleFreeInstanceGetter sigSingletonInstanceGetter3;
SingletonExample *pseSingleton4 = sigSingletonInstanceGetter3.GetInstance();
i = pseSingleton4->GetVariable();
cout << "释放了前边的实例获取器后,再次使用实例获取器获取实例成员变量值:" << i << endl;
char c;
cin >> c;
return 0;
}
运行结果如下
实例成员变量初始值:0
改变实例成员变量以后的值:10
不释放前一实例获取器的情况下使用新实例获取器获得的实例成员变量的值:10
不释放前一实例获取器的情况下使用新实例获取器再次获得的实例成员变量的值:10
释放了前边的实例获取器后,再次使用实例获取器获取实例成员变量值:0
通过例子的结果,可以看出单例类的实例pseSingleton1、pseSingleton2、pseSingleton3的内容是同一个SingletonExample类实例,pseSingleton1改变了实例值后pseSingleton2、pseSingleton3指向的内容一起跟着改变,当上面的大括号结束后,单例类实例获取器SingletonExampleFreeInstanceGetter的实例都退出了生命周期就是被释放了,这个时候单例类的实例也跟着被释放了,所以当再次通过获取器获取单例类实例pseSingleton4的时候,其状态又回到了初始状态0,即创建了新的单例类实例,也就验证了前面单例类其实已经被释放的事实了。
5、总结
从以上的例子可以看出,使用辅助类以后,单例类开发更简单了,辅助的单例类实例获取器类也比较简单容易理解,并且以上模型完全可以满足我们前文中要求单例类应该具有的3个特点。
虽然这个辅助的单例类实例获取器很简单,但是如果为每一个单例类都去开发一个实例获取器还是有些麻烦,至少比前文中的单纯单例模式代码多,如何解决这个问题呢,我们使用模板类的方法来实现这个实例获取器,这样开发人员只需要开发单例类就可以了,具体怎么做,请看下一篇。