C++单例模式思考
1. 简单实现
单例顾名思义,就是全局唯一,有且只有一个对象,不允许同时存在多个对象,所以通常单例在C++里面有固有的实现格式——将构造函数声明成private或者protected,目的是不让外部使用者通过构造函数来创建对象。对外暴露一个静态函数获取单例对象,对内维护全局唯一的静态对象实例。具体实现如下所示:
class Singleton {
protected:
Singleton(){ }
private:
static void SingletonInit() {
single_object_ = new Singletone();
}
public:
virtual ~Singleton(){ }
static Singleton* GetInstance() {
if (NULL == single_object_) {
pthread_once(&once_, &SingletonInit);
}
return single_object_;
}
private:
static Singleton* single_object_;
static pthread_once_t once_;
};
这里用到了pthread_once
调用,目的是保证单例的创建在多线程环境下是线程安全的。对外仅仅暴露GetInstance方法来获取单例类的对象。当然这是最简单的单例的实现,那么问题来了,如何将单例的实现逻辑抽象出来作为单例基类?如何保证单例对象在对象周期结束之后能够被正确释放,而不会造成内存泄露?
2.智能指针加模板实现单例基类
为了抽象出单例基类,利用模板,将单例逻辑抽象出来,内部维护的单例对象为T*类型。与此同时,为了让单例维护的内存空间能够被正确释放,这里采用智能指针代替裸指针,具体实现如下代码所示:
#include <pthread.h>
#include <tr1/memory>
template<class T>
class Singleton {
protected:
Singleton(){ }
private:
static void SingletonInit() {
single_object_.reset( new T );
}
public:
virtual ~Singleton(){ }
static std::tr1::shared_ptr<T> GetInstance() {
if(NULL == single_object_.get()) {
pthread_once(&once_, &SingletonInit);
}
return single_object_;
}
private:
static std::tr1::shared_ptr<T> single_object_;
static pthread_once_t once_;
};
//using pthread_once() the keep multi thread safe
template<class T>
pthread_once_t Singleton<T>::once_ = PTHREAD_ONCE_INIT;
template<class T>
std::tr1::shared_ptr<T> Singleton<T>::single_object_;
有几点需要注意,由于采用了智能指针,在单例基类中,其析构函数必须声明成public(这是因为智能指针以对象的方式管理资源,在智能指针内部会调用所维护对象的析构函数来释放资源)。那么问题是如何使用单例基类呢?在这里,我结合工厂模式和单例模式,利用单例基类实现一个单例工厂,具体工厂类声明代码如下:
‘ObjectFactory.h’
#ifndef OBJECTFACTORY_
#define OBJECTFACTORY_
#include "Object.h"
#include "Singleton.h"
#include <tr1/memory>
#include <map>
using namespace std;
class Object;
class ObjectFactory:public Singleton<ObjectFactory> {
friend class Singleton<ObjectFactory>;
private:
ObjectFactory() { }
public:
~ObjectFactory(){
}
public:
//this is also thread unsafe;
tr1::shared_ptr<Object> GetObject( string name ) {
if( factory_map_.count(name) == 0 ) {
factory_map_[name].reset(new Object(name));
}
return factory_map_[name];
}
private:
map<string , tr1::shared_ptr<Object> > factory_map_;
};
#endif
在单例工厂中,单例工厂继承单例基类,并且需要声明单例基类为工厂类的友元类,这是因为单例基类会显示调用单例工厂的构造函数来创建对象,但是为了保证单例工厂构造函数不至于被外部显示调用,单例工厂构造函数必须声明为private或者protected,所以这里需要声明友元类,单例基类才可以调用单例工厂类的构造函数来构造对象。
这段代码存在一个问题,在工厂类中生产对象的时候也会存在线程安全问题,所以这里在单例工厂类生产对象的时候进行加锁,避免重复生产对象造成内存的泄露。