这篇博客实现了一种自动的Singleton的设计模式的实现,相比起通常的Singleton的实现,这种方法更加简单而且更加灵活。
Singleton设计模式
单例模式在项目中是一种很重要的设计模式,它能保证一个对象在项目中只存在一个实例。在游戏中,例如说Texture Manager,Data Table这些东西应该都是从游戏开始时就创建一个实例,一直持续到游戏关闭。
Singleton往往被视为项目中的全局对象,因此相对于那些必须从属于某些事务的对象来说,全局对象的Singleton往往更加高效而且简便。否则,如果通过下面这种方式来获得一个TextureManager往往会让人崩溃:
GetApp()->GetServices()->GetGui()->GetTextureMgr();
当然,我们可以声明为外部连接全局范围的g_TextureMgr来访问。这样虽然的确很方便,但是由于全局对象的创建和销毁次序取决于执行时候的情况,对于那些可移植的项目,这些次序无法预计,因此效果还是不太好。
以往的方法
在教科书中,对于Singleton的管理通常如下:
TextureMgr& GetTextureMgr()
{
static T s_Singleton;
return s_Singleton;
}
当然为了方便,有些项目里面使用了模版或者宏,但是本质都是一样——在第一次调用的时候进行实例化,然后在程序解释的时候进行销毁。
但是这种做法有一个缺点在于是它无法控制资源的销毁——也就是说它必须在程序结束的时候才能释放掉。换句话说,我们对于资源的把控程度还不够。如果说我们想要在游戏运行的时候关闭掉一部分的话,那么这种做法显然是无法实现的。
换言之,我们需要能够追踪到这个资源,那么也就是需要找到指向它的指针。
有这么一种方法:
class TextureMgr
{
static TextureMgr* ms_Singleton;
public:
TextureMgr()
{
ms_Singleton = this;
// ...
}
~TextureMgr()
{
ms_Singleton = NULL;
// ...
}
TextureMgr& GetSingleton()
{
return (*ms_Singleton);
}
};
如果使用上面这种方法,就可以在任何时候创建以及销毁TextureMgr,而且也可以很方便地访问Singleton。
但是还有一点不方便,那就是用于追踪Singleton的代码需要加到每个类中,代码会显得比较累赘。
自动Singleton的实现
自动Singleton是使用模版来自动定义Singleton指针,并且完成指针设置、查询和清除的工作。它还可以使用assert来确保没有把Singleton实例化多次。
但是最重要的一点——只需要从这个类进行派生就可以获得Singleton的功能了。
代码如下:
#include <cassert>
template < typename T>
class Singleton
{
static T * ms_Singleton;
public :
Singleton ()
{
assert (! ms_Singleton );
int offset = (int )( T*) 1 - ( int )(Singleton < T>*)( T *)1 ;
ms_Singleton = ( T *)( (int ) this + offset );
}
~Singleton ()
{
assert ( ms_Singleton );
ms_Singleton = NULL ;
}
static T & GetSingleton ()
{
assert ( ms_Singleton );
return (* ms_Singleton );
}
static T * GetSingletonPtr ()
{
return ms_Singleton;
}
};
template < typename T >
T * Singleton < T>:: ms_Singleton = 0 ;
上面代码中比较容易造成疑惑的地方是构造函数中的offset逻辑。这个涉及到对象比较和类型转化的问题了,在另一篇博客中又提到,传送门。
如果需要将任何类进行Singleton化,需要将其从Singleton进行公开继承即可。这种方法不会影响到类的大小,只会增加一些自动的函数调用。
当然,我们确保在使用前有对应的MyClass实例化。只要被实例化了,它就会被追踪。这样一来就可以使用MyClass::GetSingleton ()
方法来获得这个对象。
<全文完>