Head First 设计模式——单件模式(Singleton Pattern)

本文深入探讨了单件模式的两种实现方式:懒汉模式和饿汉模式。详细介绍了每种模式的特点及其适用场景,同时提供了C++代码示例,帮助读者理解如何在多线程环境下保证单件模式的正确性和效率。

单件模式(Singleton Pattern)

确保一个类只有一个实例,并提供一个全局访问点


单件模式确保程序中一个类最多只有一个实例。我们在程序中会遇到这种情况如:线程池,缓存,对话框,打印机,显卡等设备驱动程序。这些类对象只能有一个实例,如果制造多个实例,就会导致许多问题产生。

经典的单件模式实现:

#include<iostream>
using namespace std;

class Singleton{
public:
	//用getInstance()方法实例化对象,并返回这个实例
	static Singleton &getInstance(){
		if(unique == NULL){
			unique = new Singleton();
			cout << "new Singleton" << endl;
		}
		return *unique;
	}
	static void release(){
		if (unique != NULL)
			delete unique;
	}
	
private:
	//把构造器声明为私有的,只有自Singleton类内
	//才可以调用构造函数
	Singleton(){
	}
	// 防止外界删除
	~Singleton(){
		cout <<" delete Singleton" << endl;
	}
	//复制构造函数和= 重载都要保护起来
	Singleton(const Singleton&);
	Singleton& operator=(const Singleton&);
	//利用一个静态变量来记录Singleton类的唯一实例
	static Singleton *unique;
};
Singleton * Singleton::unique = NULL;

int main(){
	Singleton::getInstance();
	Singleton::getInstance();
	Singleton::release();
	return 0;
}
运行结果:


上面是一个比较经典的实现,他的主要问题是——在多线程条件下不能保证只有一个实例被创建,考虑一个下面的执行顺序:

Head First 实例用Java 来实现的,加上自己对C++ 的不是很熟悉,一下内容参考了下面两篇博文

 http://www.cnblogs.com/ccdev/archive/2012/12/19/2825355.html

http://blog.yangyubo.com/2009/06/04/best-cpp-singleton-pattern/


在上面的实现中,只有当用户需要用到Instance的时候,程序才会创建一个新的实例,这被称作延迟实例化(对应着“懒汉模式”)

如果应用程序总是创建并使用单件实例,或者在创建和运行时方面的负担不太繁重,我们可以急切地创建此单例(对应着“饿汉模式”)

饿汉模式 :

即无论是否调用该类的实例,在程序开始时就会产生一个该类的实例,并且在以后返回此实例。由静态初始化实例保证其线程安全性,因为静态实例初始化在程序开始时进入主函数之前就由主线程以单线程方式完成了初始化,不必担心过多的线程问题,故在性能要求较高时,应使用这种模式,避免频繁的锁争夺

class Singleton {
public:
  static Singleton* getInstance() {
    return &m_pInstance;
  }
private:
  Singleton(){
	  cout << "new Singleton" << endl;
  };              // ctor is hidden
  ~Singleton(){
	  cout << "delete Singleton" << endl;
  }
  Singleton(Singleton const&);    // copy ctor is hidden
  Singleton& operator=(const Singleton&);
  static Singleton m_pInstance;
};

// in Singleton.cpp we have to add
Singleton Singleton::m_pInstance;

这种模式的问题也很明显, 类现在是多态的, 但静态成员变量初始化顺序还是没保证.

还引起另外一个问题 (我之前碰到过的真实事件, 以后便一直采用下面提到的 "懒汉模式"): 有两个单例模式的类 ASingleton 和 BSingleton, 某天你想在 BSingleton 的构造函数中使用 ASingleton 实例, 这就出问题了. 因为 BSingleton m_pInstance 静态对象可能先 ASingleton 一步调用初始化构造函数, 结果 ASingleton::Instance() 返回的就是一个未初始化的内存区域, 程序还没跑就直接崩掉.

懒汉模式

多线程安全的实现需要用锁,使用double-check来保证thread safety,但是如果处理大量数据时,该锁成为严重的性能瓶颈

1. 静态成员实例的懒汉模式

class Singleton
 {
 private:
     static Singleton* m_instance;
     Singleton(){}
 public:
     static Singleton* getInstance();
 };
 
 Singleton* Singleton::getInstance()
 {
     if(NULL == m_instance)
     {
         Lock();//借用其它类来实现,如boost
         if(NULL == m_instance)
         {
             m_instance = new Singleton;
         }
         UnLock();
     }
     return m_instance;
 }

Instance() 只在第一次被调用时为 m_instance 分配内存并初始化. 看上去所有的问题都解决了, 初始化顺序有保证, 多态也没问题.不过细心的你可能已经发现了一个问题, 程序退出时, 析构函数没被执行. 这在某些设计不可靠的系统上会导致资源泄漏, 比如文件句柄, socket 连接, 内存等等. 幸好 Linux / Windows 2000/XP 等常用系统都能在程序退出时自动释放占用的系统资源. 不过这仍然可能是个隐患, 至少 J. Nakamura 印象中, 有些系统是不会自动释放的.

对于这个问题, 比较土的解决方法是, 给每个 Singleton 类添加一个 destructor() 方法:

virtual bool destructor() {
    // ... release resource
    if (NULL!= m_instance) {
		delete m_instance;
        m_instance = NULL;
    }
}

然后在程序退出时确保调用了每个 Singleton 类的  destructor()  方法, 这么做虽然可靠, 但却很是繁琐. 幸运的是, Meyers 大师有个更简便的方法.


2. 内部静态实例的懒汉模式

这里需要注意的是,C++ 0x 以后,要求编译器保证内部静态变量的线程安全性,可以不加锁。但C++ 0x 以前仍需加锁。

class Singleton {
public:
  static Singleton& Instance() {
	  //lock()      not needed after C++ 0x
		static Singleton instance;
		return instance;
		//Unlock()
  }
private:
	Singleton(){
		cout << "Singleton" << endl;
	};          // ctor is hidden
	~Singleton(){
		cout <<"~Singleton" << endl;
	}
	Singleton(Singleton const&);      // copy ctor is hidden
	Singleton& operator=(Singleton const&);  // assign op is hidden
};
int main(){
	Singleton::Instance();
	Singleton::Instance();
	return 0;
}

运行结果:


在 Instance() 函数内定义局部静态变量的好处是, instance 的构造函数只会在第一次调用 Instance() 时被初始化, 达到了动态初始化效果, 保证了成员变量和 Singleton 本身的初始化顺序.

它还有一个潜在的安全措施, Instance() 返回的是对局部静态变量的引用, 如果返回的是指针,Instance() 的调用者很可能会误认为他要检查指针的有效性, 并负责销毁. 构造函数和拷贝构造函数也私有化了, 这样类的使用者不能自行实例化.

另外, 多个不同的 Singleton 实例的析构顺序与构造顺序相反.



评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值