zoukankan      html  css  js  c++  java
  • C++中的单例模式

            单例模式也称为单件模式、单子模式,可能是使用最广泛的设计模式。其意图是保证一个类仅有一个实例,并提供一个訪问它的全局訪问点,该实例被全部程序模块共享。有非常多地方须要这种功能模块,如系统的日志输出,GUI应用必须是单鼠标,MODEM的联接须要一条且仅仅须要一条电话线,操作系统仅仅能有一个窗体管理器,一台PC连一个键盘。
           单例模式有很多种实现方法,在C++中,甚至可以直接用一个全局变量做到这一点,但这种代码显的非常不优雅。 使用全局对象可以保证方便地訪问实例,可是不能保证仅仅声明一个对象——也就是说除了一个全局实例外,仍然能创建同样类的本地实例。
    《设计模式》一书中给出了一种非常不错的实现,定义一个单例类,使用类的私有静态指针变量指向类的唯一实例,并用一个公有的静态方法获取该实例。
           单例模式通过类本身来管理其唯一实例,这样的特性提供了解决这个问题的方法。唯一的实例是类的一个普通对象,但设计这个类时,让它仅仅能创建一个实例并提供对此实例的全局訪问。唯一实例类Singleton在静态成员函数中隐藏创建实例的操作。习惯上把这个成员函数叫做Instance(),它的返回值是唯一实例的指针。

    定义例如以下:

    class CSingleton
    {
    private:
    	CSingleton()   //构造函数是私有的
    	{
    	}
    	static CSingleton *m_pInstance;
    public:
    	static CSingleton * GetInstance()
    	{
    		if(m_pInstance == NULL)  //推断是否第一次调用
    			m_pInstance = new CSingleton();
    		return m_pInstance;
    	}
    };
    用户訪问唯一实例的方法仅仅有GetInstance()成员函数。假设不通过这个函数,不论什么创建实例的尝试都将失败,由于类的构造函数是私有的。GetInstance()使用懒惰初始化,也就是说它的返回值是当这个函数首次被訪问时被创建的。这是一种防弹设计——全部GetInstance()之后的调用都返回同样实例的指针:

    CSingleton* p1 = CSingleton :: GetInstance();
    CSingleton* p2 = p1->GetInstance();
    CSingleton & ref = * CSingleton :: GetInstance();
    对GetInstance稍加改动,这个设计模板便能够适用于可变多实例情况,如一个类同意最多五个实例。
     
    单例类CSingleton有下面特征:
    它有一个指向唯一实例的静态指针m_pInstance,而且是私有的;
    它有一个公有的函数,能够获取这个唯一的实例,而且在须要的时候创建该实例;
    它的构造函数是私有的,这样就不能从别处创建该类的实例。
    大多数时候,这种实现都不会出现故障。有经验的读者可能会问,m_pInstance指向的空间什么时候释放呢?更严重的问题是,该实例的析构函数什么时候运行?
    假设在类的析构行为中有必须的操作,比方关闭文件,释放外部资源,那么上面的代码无法实现这个要求。我们须要一种方法,正常的删除该实例。
    能够在程序结束时调用GetInstance(),并对返回的指针掉用delete操作。这样做能够实现功能,但不仅非常丑陋,并且easy出错。由于这种附加代码非常easy被忘记,并且也非常难保证在delete之后,没有代码再调用GetInstance函数。
    一个妥善的方法是让这个类自己知道在合适的时候把自己删除,或者说把删除自己的操作挂在操作系统中的某个合适的点上,使其在恰当的时候被自己主动运行。
    我们知道,程序在结束的时候,系统会自己主动析构全部的全局变量。其实,系统也会析构全部的类的静态成员变量,就像这些静态成员也是全局变量一样。利用这个特征,我们能够在单例类中定义一个这种静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例。如以下的代码中的CGarbo类(Garbo意为垃圾工人):
    class CSingleton
    {
    private:
    	CSingleton()
    	{
    	}
    	static CSingleton *m_pInstance;
    	class CGarbo   //它的唯一工作就是在析构函数中删除CSingleton的实例
    	{
    	public:
    		~CGarbo()
    		{
    			if(CSingleton::m_pInstance)
    				delete CSingleton::m_pInstance;
    		}
    	};
    	static CGarbo Garbo;  //定义一个静态成员变量,程序结束时,系统会自己主动调用它的析构函数
    public:
    	static CSingleton * GetInstance()
    	{
    		if(m_pInstance == NULL)  //推断是否第一次调用
    			m_pInstance = new CSingleton();
    		return m_pInstance;
    	}
    };
    
    类CGarbo被定义为CSingleton的私有内嵌类,以防该类被在其它地方滥用。
    程序执行结束时,系统会调用CSingleton的静态成员Garbo的析构函数,该析构函数会删除单例的唯一实例。
    使用这样的方法释放单例对象有下面特征:
    在单例类内部定义专有的嵌套类;
    在单例类内定义私有的专门用于释放的静态成员;
    利用程序在结束时析构全局变量的特性,选择终于的释放时机;
    使用单例的代码不须要不论什么操作,不必关心对象的释放。


    进一步的讨论

    可是加入一个类的静态对象,总是让人不太惬意,所以有人用例如以下方法来又一次实现单例和解决它对应的问题,代码例如以下:

    class CSingleton
    {
    private:
    	CSingleton()   //构造函数是私有的
    	{
    	}
    public:
    	static CSingleton & GetInstance()
    	{
    		static CSingleton instance;   //局部静态变量
    		return instance;
    	}
    };
    使用局部静态变量,很强大的方法,全然实现了单例的特性,并且代码量更少,也不用操心单例销毁的问题。
    但使用此种方法也会出现故障,当例如以下方法使用单例时问题来了,
    Singleton singleton = Singleton :: GetInstance();
    这么做就出现了一个类拷贝的问题,这就违背了单例的特性。产生这个问题原因在于:编译器会为类生成一个默认的构造函数,来支持类的拷贝。

    最后没有办法,我们要禁止类拷贝和类赋值,禁止程序猿用这样的方式来使用单例,当时领导的意思是GetInstance()函数返回一个指针而不是返回一个引用,函数的代码改为例如以下:

    class CSingleton
    {
    private:
    	CSingleton()   //构造函数是私有的
    	{
    	}
    public:
    	static CSingleton * GetInstance()
    	{
    		static CSingleton instance;   //局部静态变量
    		return &instance;
    	}
    };

    但我总觉的不好,为什么不让编译器不这么干呢。这时我才想起能够显示的声明类拷贝的构造函数,和重载 = 操作符,新的单例类例如以下:

    class CSingleton
    {
    private:
    	CSingleton()   //构造函数是私有的
    	{
    	}
    	CSingleton(const CSingleton &);
    	CSingleton & operator = (const CSingleton &);
    public:
    	static CSingleton & GetInstance()
    	{
    		static CSingleton instance;   //局部静态变量
    		return instance;
    	}
    };
    关于Singleton(const Singleton);和 Singleton & operate = (const Singleton&);函数,须要声明成私有的,而且仅仅声明不实现。这样,如果用上面的方式来使用单例时,无论是在友元类中还是其它的,编译器都是报错。
    不知道这种单例类是否还会有问题,但在程序中这样子使用已经基本没有问题了。


    考虑到线程安全、异常安全,能够做下面扩展
    class Lock
    {
    private:       
    	CCriticalSection m_cs;
    public:
    	Lock(CCriticalSection  cs) : m_cs(cs)
    	{
    		m_cs.Lock();
    	}
    	~Lock()
    	{
    		m_cs.Unlock();
    	}
    };
    
    class Singleton
    {
    private:
    	Singleton();
    	Singleton(const Singleton &);
    	Singleton& operator = (const Singleton &);
    
    public:
    	static Singleton *Instantialize();
    	static Singleton *pInstance;
    	static CCriticalSection cs;
    };
    
    Singleton* Singleton::pInstance = 0;
    
    Singleton* Singleton::Instantialize()
    {
    	if(pInstance == NULL)
    	{   //double check
    		Lock lock(cs);           //用lock实现线程安全,用资源管理类,实现异常安全
    		//使用资源管理类,在抛出异常的时候,资源管理类对象会被析构,析构总是发生的不管是由于异常抛出还是语句块结束。
    		if(pInstance == NULL)
    		{
    			pInstance = new Singleton();
    		}
    	}
    	return pInstance;
    }

    之所以在Instantialize函数里面对pInstance 是否为空做了两次推断,由于该方法调用一次就产生了对象,pInstance == NULL 大部分情况下都为false,假设依照原来的方法,每次获取实例都须要加锁,效率太低。而改进的方法仅仅须要在第一次 调用的时候加锁,可大大提高效率。


  • 相关阅读:
    delphi7在windows server 2003企业版上不能打开项目的选项(Options)窗口的解决方法
    简单的两个字“谢谢”,会让我坚持我的写作,我也要谢谢你们
    F41GUT 安装Windows server 2003系统后无法安装显卡驱动的解决办法
    远程桌面无法登录windows server 2003服务器
    F41GUT 安装Windows server 2003系统后无法安装显卡驱动的解决办法
    MS SQL Server 2000版在windows server 2003企业版系统上运行时造成数据库suspect的解决方法
    delphi7在windows server 2003企业版上不能打开项目的选项(Options)窗口的解决方法
    远程桌面无法登录windows server 2003服务器
    MS SQL Server 2000版在windows server 2003企业版系统上运行时造成数据库suspect的解决方法
    关于ajax 和josn
  • 原文地址:https://www.cnblogs.com/lcchuguo/p/4081589.html
Copyright © 2011-2022 走看看