C++设计模式——单例模式

1、单例模式

何为单例模式,在GOF的《设计模式:可复用面向对象软件的基础》中是这样说的:保证一个类只有一个实例,并提供一个访问它的全局访问点。首先,需要保证一个类只有一个实例;在类中,要构造一个实例,就必须调用类的构造函数,如此,为了防止在外部调用类的构造函数而构造实例,需要将构造函数的访问权限标记为protected或private;最后,需要提供要给全局访问点,就需要在类中定义一个static函数,返回在类内部唯一构造的实例。意思很明白,使用UML类图表示如下。


2、单例模式的应用场景

有一些对象其实只需要一个,比如:线程池,缓存,对话框,处理偏好设置和注册表的对象,日志对象,充当打印机,显卡等设备的驱动程序对象。这些对象只能够拥有一个实例,如果创建出了多个实例,就会导致一些程序的问题。程序的行为异常,资源使用的过量,或者导致不一致的结果。常用来管理共享的资源,比如数据库的连接或者线程池。

3、单例模式的实现

单例模式,单从UML类图上来说,就一个类,没有错综复杂的关系。但是,在实际项目中,使用代码实现时,还是需要考虑很多方面的。

1)不好的写法一:只适合在单线程环境

由于要求只能生成一个实例,因此我们必须把构造函数设为私有函数以禁止他人创建实例。我们可以定义一个静态的实例,在需要的时候创建该实例。下面定义定义的Singleton就是基于这个思路的实现:

#include<iostream>
#include<string.h>
using namespace std;
class Singleton 
{
private:
    Singleton(){}
    static Singleton* _instance;
public:
    ~Singleton();
    static Singleton* getInstance();
};

Singleton* Singleton::_instance = NULL;

Singleton* Singleton::getInstance()
{
    if(_instance==NULL) _instance = new Singleton();
    return _instance;
}

int main(int argc,char* argv[])
{
    Singleton* g_singleton = Singleton::getInstance();
    return 0;
}
这是最简单,也是最普遍的实现方式,代码在单线程的时候能够正常工作,但在多线程的情况下就有问题了。设想如果两个线程同时运行到判断_instance是否位NULL的if语句,并且_instance的确还没有创建时,那么两个线程都会创建一个实例,此时类型Singleton就不再满足单例模式的要求了。为了保证在多线程环境下我们还是只能得到类型的一个实例,需要加上一个同步锁。

2)不好的写法二:虽然在多线程环境中能工作但效率不高

#include<iostream>
#include<string.h>
#include <pthread.h>
using namespace std;
class Singleton 
{
private:
    Singleton();
    static Singleton* _instance;
	static pthread_mutex_t mutex;
public:
    ~Singleton();
    static Singleton* getInstance();
};
pthread_mutex_t Singleton::mutex;
Singleton* Singleton::_instance = NULL;

Singleton::Singleton()
{
	pthread_mutex_init(&mutex,NULL);
}

Singleton* Singleton::getInstance()
{
	pthread_mutex_lock(&mutex);
    if(_instance==NULL) _instance = new Singleton();
	pthread_mutex_unlock(&mutex);
    return _instance;
}

int main(int argc,char* argv[])
{
    Singleton* g_singleton = Singleton::getInstance();
    return 0;
}
我们还是假设有两个线程同时想创建一个实例。由于在一个时刻只有一个线程能得到同步锁,当第一线程加上锁时,第二个线程只能等待。当第一个线程发现实例还没有创建时,他创建出一个实例。接着第一个线程释放同步锁,此时第二个线程可以加上同步锁,并运行接下来的代码。这个时候由于实例已经被第一个线程创建出来了,第二个线程就不会重复创建实例了,这样就保证了我们在多线程环境中也只能得到一个实例。但是这个类还不是很完美。我们每次通过属性_instance得到Singleton的实例,都会试图加上一个同步锁,而枷锁是一个非常耗时的操作,在没有必要的时候我们应该尽量避免。

3)可行的解法:加同步锁前后两次判断实例是否已存在

我们只有在实例还没有创建之前需要加锁操作,以保证只有一个线程创建出实例。而当实例已经创建之后,我们已经不需要再做加锁操作了。于是我们可以把上述代码再做进一步的改进:

#include<iostream>
#include<string.h>
#include <pthread.h>
using namespace std;
class Singleton 
{
private:
    Singleton();
    static Singleton* _instance;
	static pthread_mutex_t mutex;
public:
    ~Singleton();
    static Singleton* getInstance();
};
pthread_mutex_t Singleton::mutex;
Singleton* Singleton::_instance = NULL;

Singleton::Singleton()
{
	pthread_mutex_init(&mutex,NULL);
}

Singleton* Singleton::getInstance()
{
	if(_instance==NULL) 
	{
		pthread_mutex_lock(&mutex);
    	if(_instance==NULL) _instance = new Singleton();
		pthread_mutex_unlock(&mutex);
	}
    return _instance;
}

int main(int argc,char* argv[])
{
    Singleton* g_singleton = Singleton::getInstance();
    return 0;
}
上述代码只有当_instance为NULL即没有创建时,需要加锁操作。当_instance已经创建出来之后,则无须加锁。因为只有第一次的时候_instance为NULL,因此只在第一次试图创建实例的时候需要加锁。这样方法三的时间效率比方法二要好很多。方法三用加锁机制来确保在多线程环境下只创建一个实例,并且用两个if判断来提高效率。这样的代码实现起来比较复杂,容易出错。另一方面这种实现方法在平时的项目开发中用的很好,也没有什么问题?但是,如果进行大数据的操作,加锁操作将成为一个性能的瓶颈。

4)推荐方法一:利用静态变量初始化的特性

#include<iostream>
#include<string.h>
using namespace std;

class Singleton 
{
private:
    Singleton(){}
    static Singleton* _instance;
public:
    ~Singleton();
    static Singleton* getInstance();
};

//外部初始化,在进入主函数之前被初始化
Singleton* Singleton::_instance = new Singleton();

Singleton* Singleton::getInstance()
{
    return _instance;
}

int main(int argc,char* argv[])
{
    Singleton* g_singleton = Singleton::getInstance();
    return 0;
}
因为静态初始化在程序开始时,也就是进入主函数之前,由主线程以单线程方式完成了初始化,所以静态初始化实例保证了线程安全性。在性能要求比较高时,就可以使用这种方式,从而避免频繁的加锁和解锁造成的资源浪费。

5)推荐方法二:利用静态局部变量的特性,C++0X以后,编译器已经保证内部静态变量的线程安全性

上边我们没有提到单例的消毁,此种实现就是为了使单例的消毁简单化:

#include<iostream>
#include<string.h>
using namespace std;

class Singleton 
{
private:
    Singleton(){}
public:
    static Singleton* getInstance();
};

Singleton* Singleton::getInstance()
{
	static Singleton _instance;
    return &_instance;
}

int main(int argc,char* argv[])
{
    Singleton* g_singleton = Singleton::getInstance();
    return 0;
}

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

#include<iostream>
#include<string.h>
using namespace std;

class Singleton 
{
private:
    Singleton(){}
public:
    static Singleton* getInstance();
};

Singleton* Singleton::getInstance()
{
    //这里加锁和解锁只是说明,C++没有现成的实现
    lock();
	static Singleton _instance;
	unlock();
    return &_instance;
}

int main(int argc,char* argv[])
{
    Singleton* g_singleton = Singleton::getInstance();
    return 0;
}
4、实例的消毁

在上述的五种方法中,除了第五种没有使用new操作符实例化对象以外,其余四种都使用了;我们一般的编程观念是,new操作是需要和delete操作进行匹配的;是的,这种观念是正确的。那么我们能否直接在析构函数里delete释放对象呢?直接看代码:

#include<iostream>
#include<string.h>
using namespace std;
class Singleton 
{
private:
    Singleton(){}
    static Singleton* _instance;
public:
    ~Singleton();
    static Singleton* getInstance();
};

Singleton* Singleton::_instance = NULL;

Singleton::~Singleton()
{
    if(_instance)
    {
		cout << "release _instance" << endl;
        delete _instance;
        _instance = NULL;
    }
}

Singleton* Singleton::getInstance()
{
    if(_instance==NULL) _instance = new Singleton();
    return _instance;
}

int main(int argc,char* argv[])
{
    Singleton* g_singleton = Singleton::getInstance();
    return 0;
}

程序什么输出也没有,可以看到并没有调用单例的析构函数,需要清楚以下几点:
1)单例中的 new 的对象需要delete释放。
2)delete释放对象的时候才会调用对象的析构函数。
3)如果在析构函数里调用delete,那么程序结束时,根本进不去析构函数,怎么会delete。
4)如果程序结束能自动析构,那么就会造成一个析构的循坏,所以new对应于delete。

所以单例的析构函数不会自动调用,那么可以写一个成语函数来析构实例,在使用后手动调用,代码如下:

#include<iostream>
#include<string.h>
using namespace std;
class Singleton 
{
private:
    Singleton(){}
    static Singleton* _instance;
public:
    static void destroy();
    static Singleton* getInstance();
};

Singleton* Singleton::_instance = NULL;

void Singleton::destroy()
{
    if(_instance)
    {
		cout << "release _instance" << endl;
        delete _instance;
        _instance = NULL;
    }
}

Singleton* Singleton::getInstance()
{
    if(_instance==NULL) _instance = new Singleton();
    return _instance;
}

int main(int argc,char* argv[])
{
    Singleton* g_singleton = Singleton::getInstance();
	g_singleton->destroy();
    return 0;
}

可以看到实例被消毁了。这也是最简单,最普通的处理方法了;但是,很多时候,我们是很容易忘记调用destroy函数,就像你忘记了调用delete操作一样。由于怕忘记delete操作,所以就有了智能指针;那么,在单例模型中,没有“智能单例”,该怎么办?

一个妥善的方法是让这个类自己知道在合适的时候把自己删除。或者说把删除自己的操作挂在系统中的某个合适的点上,使其在恰当的时候自动被执行。由于程序在结束的时候,系统会自动析构所有的全局变量,实际上,系统也会析构所有类的静态成员变量,就像这些静态变量是全局变量一样。我们知道,静态变量和全局变量在内存中,都是存储在静态存储区的,所以在析构时,是同等对待的。利用这个特征,我们可以在单例类中定义一个这样的静态成员变量,而它的唯一工作就是在析构函数 中删除单例类的实例。如下面的代码中的CGarbo类(Garbo意为垃圾工人):

#include<iostream>
#include<string.h>
using namespace std;
class Singleton 
{
private:
    Singleton(){}
    static Singleton* _instance;
	class CGarbo
	{
	public :
		~CGarbo()
		{
			if (_instance != NULL )
			{
				cout<< "release _instance" <<endl;
				delete _instance;
				_instance = NULL ;
			}
		}
	};
	static CGarbo cg;
public:
    static Singleton* getInstance();
};

Singleton* Singleton::_instance = NULL;
Singleton::CGarbo Singleton::cg;

Singleton* Singleton::getInstance()
{
    if(_instance==NULL) _instance = new Singleton();
    return _instance;
}

int main(int argc,char* argv[])
{
    Singleton* g_singleton = Singleton::getInstance();
    return 0;
}

类CGarbo被定义为Singleton的私有内嵌类,以防该类被在其它地方滥用。
在程序运行结束时,系统会调用Singleton的静态成员Garbo的析构函数,该析构函数会删除单例的唯一实例。而这种资源的释放方式是在程序员“不知道”的情况下进行的,程序员不用特别的去关心,使用单例模式的代码时,不必关心资源的释放。
使用这种方法释放单例对象有以下特征:
1)在单例类内部定义专有的嵌套类。
2)在单例类内定义私有的专门用于释放的静态成员。
3)利用程序在结束时析构全局变量的特性,选择最终的释放时机。
4)使用单例的代码不需要任何操作,不必关心对象的释放

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值