2007年7月19日星期四

Singleton (单件) 设计模式

[2007.04.01]

最近在苏州一家计算机公司工作。因为大量用到了Singleton模式,而原来自己实现的Singleton模式存在内存泄漏的问题,所以花了点时间研究如何更好地实现Singleton模式。

我原来实现的Singleton模式是这样的:

(Singleton1.h)

class Singleton
{
public:
static Singleton* Instance()
{
if (_instance == 0)
_instance = new Singleton;

return _instance;
}

private:
Singleton() { _testPtr = new int; }
~Singleton() { delete _testPtr; }
static Singleton* _instance;

int* _testPtr;
};


(Singleton1.cpp)

Singleton* Singleton::_instance = 0;


很明显,Singleton::_instance 只是一个指针,在程序结束的时候并不会被自动析构,因此Singleton::~Singleton() 也不会被调用,完全是一个空壳。为了使Singleton类被自动析构,一个最直接的办法就是把Singleton::_instance改成类的实例而非指针,这样静态变量_instance就会在程序结束的时候自动被析构,再设法让_instance中的析构函数调用Singleton类的析构函数,就可以解决内存泄漏的问题了。

这里有三种解决方案:
1. _instance是一个和Singleton类不相关的类(假设为SingletonDestroyer)的实例;2. _instance是Singleton类的父类的实例;3. _instance是Singleton类自己的实例。

第一种解决方案首先被排除,因为如何让SingletonDestroyer访问Singleton类的析构函数是一个问题。Singleton类可以是任何不同的类,有不同的接口,无法统一地被SingletonDestroyer处理。当然,可以让所有的Singleton类继承于一个基类,使它们具有相同的接口,再在SingletonDestroyer中保留一个此基类的指针,在SingletonDestroyer::~SingletonDestroyer()中调用基类指针的析构函数( _singleton->~Singleton(); ),但这样其实已经退化成第二种解决方案了,所以第一种解决方案被排除。

让我们来看看第二种解决方案的实现。

(Singleton2.h)

class Singleton
{
public:
Singleton() { _singleton = 0; }
~Singleton()
{
if (_singleton != 0)
{
_singleton->Destroy();
delete _singleton;
}
}

Singleton* _singleton;

protected:
virtual void Destroy() {}
};

class Sub : public Singleton
{
public:
static Sub* Instance()
{
if (_instance._singleton == 0)
_instance._singleton = new Sub;

return (Sub*)_instance._singleton;
}

private:
Sub() { _testPtr = new int; }
void Destroy() { delete _testPtr; }
static Singleton _instance;

int* _testPtr;
};


(Singleton2.cpp)

Singleton Sub::_instance = Singleton();


Sub是实际的单件类,所有Sub类都继承于Singleton类。Sub::_instance是一个Singleton类的对象,在程序结束时会被自动析构,调用Singleton::~Singleton()。Singleton类的Destroy()是提供给子类销毁自己的成员数据的,会在Singleton::~Singleton()中调用。如果子类不覆盖Destroy(),则不执行任何程序。

这个解决方案在非MFC的单线程程序中可以正常工作,但是在多线程的MFC程序中有问题(运行到afxmem.cpp的某行会出错),其他情况我没有测试。在我这里这个解决方案也被否决了。

第三种解决方案:

(singleton3.h)

class Singleton
{
public:
static Singleton* Instance() { return &_instance; }
void Setup(int intValue) { *_testPtr = intValue; }

private:
Singleton() { _testPtr = new int; }
~Singleton() { delete _testPtr; }
static Singleton _instance;

int* _testPtr;
};


(singleton3.cpp)

Singleton Singleton::_instance = Singleton();


还是第三种方案最简单。Singleton::_instance是自己类的实例,由于是静态成员,所以可以存在。程序结束时,自动调用自己类的析构函数。在MFC和非MFC、单线程和多线程中都没有问题,可以参考。
4.23更新:如果Singleton类是继承于一个父类BaseClass,那么它的_instance变量的类型和实例化都应该不变,而不是像指针那样,BaseClass* _instance; _instance = new Singleton;

另外还有一个第三种方法的变种,就是使用智能指针std::auto_ptr,代码如下:

(Singleton4.h)

#include <memory>

class Singleton
{
public:
static Singleton* Instance() { return _instance.get(); }
~Singleton() { delete _testPtr; }
void Setup(int intValue) { *_testPtr = intValue; }

private:
Singleton() { _testPtr = new int; }
static auto_ptr<singleton> _instance;

int* _testPtr;
};

(Singleton4.cpp)

auto_ptr<singleton> Singleton::_instance(new Singleton);


[2007.07.19 更新]

其实既然Singleton的生存周期贯穿整个程序,那么必然只有在程序结束的时候才会析构Singleton类。既然程序都结束了,操作系统也会自动回收所有相关内存,那么Singleton类的析构就显得多余了。

因此,又写了一个Singleton的模板类,更方便一点了:

(Singleton5.h)

template <typename T>
class Singleton
{
static T* _instance;

Singleton() {};

public:
static T* Instance()
{
if (_instance == NULL)
_instance = new T;

return _instance;
}
};

(Singleton5.cpp)

template <typename T>
T* Singleton<T>::_instance = NULL;

注意,Singleton的相关实现也要放到头文件里。使用时,所有类继承于Singleton,并传入类自己作为模板参数。如果子类希望有private的构造函数,则还需要让自己和Singleton类成为友元,因为Singleton里有new T,会访问子类的构造函数,而并没有什么修饰符可以指定只允许父类访问,而不许其他对象访问。

如:

class Sub : public Singleton<Sub>
{
friend class Singleton<Sub>;
Sub();

public:
int testFunc() { return 0; }
};

main()
{
Sub::Instance()->testFunc();
}

总结一下。某些类一个程序执行期间只需要存在一个副本。通常这种类仅仅提供某些功能,不存储任何数据,或者仅存储程序的全局数据。对于前者,只需要将类的所有函数设为静态函数即可。对于后者,则使用单件模式。一个类由普通类转换成单件类不需要做任何修改,只需要添加_instance成员变量和Instance()成员函数。

1 条评论:

洪亮劼 说...

我也写了一篇单件的:)
你可以看看。我现在主要是想关于单件如何能够继承使用的问题。