首页 > 代码库 > 单例模式初探

单例模式初探

大致思路是,将该类的构造函数定义为私有方法,代码其他地方不能实例化该对象,只能通过调用该类的一个静态成员函数(get_instance())来获取这个唯一实例。
更进一步,把该类的复制构造函数和重载的=赋值运算也声明为私有,即Singleton(const Singleton)和 Singleton & operate = (const Singleton&)函数,需要声明成私有的,并且只声明不实现.
这里有个小坑是,当这个唯一的实例没有被创建时,有多个线程同时调用get_instance(),可能会造成过个实例的创建。因此需要牺牲一点效率做处理,如:加锁和双检测(double-check)以保护。

对于实例的创建,又有饿汉和懒汉两种模式,懒汉即在使用对象时才进行创建,如上面提到的,存在线程安全性问题。有一点补充的是,C++0X以后,要求编译器保证内部静态变量的线程安全性,可以不加锁。但C++ 0X以前,仍需要加锁。

而饿汉模式是,在进入主函数之前就由主线程以单线程方式完成了初始化,不必担心多线程问题,在性能需求较高时,应使用这种模式,避免频繁的锁争夺。但是多个单例模式的类相互引用时,由于静态成员变量 初始化顺序没有保障,会有坑出现,详见:
http://blog.csdn.net/crayondeng/article/details/24853471

以一个日志类为例:
考虑到日志文件只有一个的特点,我们用一个单例模式来实现,在其构造函数中完成对日志文件的初始化(打开和基本设置),其后每次读写操作只需拿到有效的文件句柄。

类的声明如下:
class Logger{

	public:
		static Logger* get_instance();
		int log_write(String &str, int errcode);
		int log_read(String &str, int errcode);
		static int mutex_init();

		static pthread_mutex_t *mutex_for_creating;
	private:
		Logger();
		~Logger();

		Logger(const Logger &);
		Logger & operator = (const Logger &);

		static Logger* p_logger;
		int fd;
};

懒汉模式获取实例的函数大致如下:
static Logger* Logger::get_instance()
{
	if (p_logger)
		return p_logger;

	pthread_mutex_lock(&mutex_for_creating);
	if (!p_logger) {
		p_logger = new Logger;
	}
	pthread_mutex_unlock(&mutex_for_creating);
	return p_logger;
}

两个if语句即上面提到的双检测机制,确保线程安全。

进入程序前有两个初始化:
static Logger::p_logger = NULL;

static int Logger::mutex_init()
{
	if ((mutex_for_creating = new pthread_mutex_t) == NULL)
		return -1;
	
	pthread_mutex_init(&mutex_for_creating, NULL);
	return 0;
}

而对于饿汉模式:
将实例的指针p_logger置为public,直接在进入主程序前new一个对象:
static Logger::p_logger = new Logger;


关于对象生命周期,一般可以不考虑,因为该对象通常是存在在整个程序的生命周期,随着程序的退出自动销毁。
而有些情况,如:“在类中,有一些文件锁了,文件句柄,数据库连接等等,这些随着程序的关闭而不会立即关闭的资源,必须要在程序关闭前,进行手动释放”[1]

也有一些参考的方法,如添加CGarbo类专门对单例对象进行回收,或者是用静态局部变量,如:http://blog.yangyubo.com/2009/06/04/best-cpp-singleton-pattern/和http://blog.csdn.net/Hackbuteer1/article/details/7460019
以及http://www.programlife.net/cpp-singleton-memory-retrieve.html
静态局部变量具有局部作用域,它只被初始化一次,自从第一次被初始化直到程序运行结束都一直存在,它和全局变量的区别在于全局变量对所有的函数都是可见的,而静态局部变量只对定义自己的函数体始终可见。
简单地说static 局部变量相对于局部变量:改变局部变量的存储位置,不改变局部变量的作用范围。



参考:[1] http://www.jellythink.com/archives/82

单例模式初探