单实例
我们在设计某些类时,根据需求,有时只需要一个实例,不想过多浪费内存,这就可以通过单实例的方式。
如,我们在使用windows的任务管理器时,弹出的始终是一个窗口,
这里如果对象采用多实例模式,那么就会弹出多个窗口,会出现下述两种情况:
第一,如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,
容易使用户误解,不知道哪一个才是真实的状态,这与需求不符;
第二,如果这些窗口显示的内容完全一致,则是重复对象,意味着浪费内存资源。
因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。
单实例的特点及实现
单实例模式,应该有如下特点:
第一,这个类只能创建一个对象;
第二,这个类必须自行创建这个对象;
第三,这个类创建的对象必须在整个系统都可以使用。
单实例上述特点的实现方式:
第一,是这个类只提供私有的构造函数;
第二,是这个类定义中含有一个该类的静态私有对象;
第三,是这个类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。
思考:
Singleton模式经常和Factory(AbstractFactory)模式在一起使用,
因为系统中工厂对象一般来说只要一个
单实例的具体代码实现
需要考虑:
第一,只有一个对象,则需要禁止copying行为(包括拷贝和赋值)。
第二,如果是动态申请资源,则需要考虑资源的释放。
第三,多线程安全,即同一时刻多个线程调用这个函数会不会出现程序异常。
(1) 懒汉式
懒汉式的方式就是延迟加载,即定义一个指向该类的私有指针,
直到用到实例的时候才进行对象的创建,并把对象地址赋值给这个指针。
第一种方式:申请的资源手动释放
############# Test.h #############
class Test
{
public:
//获取单例指针
static Test* getInstance();
//销毁单例资源 注:必须在所有线程结束后,进程结束前调用, 或不调用
static void destroyInstance();
private:
//防止外部构造和析构
Test();
~Test();
//防止拷贝和赋值, 只声明实现
Test(Test&);
Test& operator=(Test&);
private:
//全局唯一对象指针
static Test* m_pTest;
//针对m_pTest的全局锁,实现创建单实例线程安全, 锁的封装,参考本博客线程锁的封装
static Lock m_sLock;
};
############# Test.cpp #############
Test* Test::m_pTest = NULL;
Lock Test::m_sLock;
/*
* 构造函数
*/
Test::Test()
{
}
/*
* 析构函数
*/
Test::~Test()
{
}
//创建单实例对象
Test* Test::getInstance()
{
Test::m_sLock.mutexLock();
if(m_pTest == NULL)
{
m_pTest = new(std::nothrow) Test;
}
Test::m_sLock.mutexUnlock();
return m_pTest;
}
//销毁Test, 注:必须在所有线程结束后,进程结束前调用
void Test::destroyInstance()
{
Test::m_sLock.mutexLock();
delete m_pTest;
m_pTest = NULL;
Test::m_sLock.mutexUnlock();
}
说明:
1. 创建实例,getInstance()
2. 销毁实例,destroyInstance()
3. 在懒汉式的单例类中,其实有两个状态,单例未初始化和单例已经初始化。
假设单例还未初始化,有两个线程同时调用getInstance方法,这时执行 m_pTest == NULL 肯定为真,
然后两个线程都初始化一个单例,最后得到的指针并不是指向同一个地方,
不满足单例类的定义了,所以懒汉式的写法会出现线程安全的问题!
在多线程环境下,要对其进行修改,例如,上面进行加锁处理,
以此来保证只有一个实例。
4. 构造函数为私有的好处:
禁止用户对此类型的变量进行定义,即禁止在类外创建此类型的对象。
5. 析构函数为私有的好处:
禁止用户在程序中使用 delete 删除此类型对象。
对象的删除只能在类内实现,也就是说只有类的实现者才有可能实现对对象的 delete,
用户不能随便删除对象。如果用户想删除对象的话,只能按照类的实现者提供的方法进行。
第二种:申请的资源自动释放(这种方式有点多余,程序结束,系统自动回收内存,只是一种思考)
############# Test.h #############
class Test
{
public:
//获取单例指针
static Test* getInstance();
private:
//防止外部构造和析构
Test();
~Test();
//防止拷贝和赋值, 只声明实现
Test(Test&);
Test& operator=(Test&);
class Garbo //它的唯一工作就是在析构函数中删除Test的实例
{
public:
~Garbo()
{
if(Test::m_pTest)
{
delete Test::m_pTest;
}
}
};
private:
//全局唯一对象指针
static Test* m_pTest;
//定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数
static Garbo m_sGarbo;
//针对m_pTest的全局锁,实现创建单实例线程安全, 锁的封装,参考本博客线程锁的封装的文章
static Lock m_sLock;
};
############# Test.cpp #############
Test* Test::m_pTest = NULL;
Garbo Test::m_sGarbo;
Lock Test::m_sLock;
/*
* 构造函数
*/
Test::Test()
{
}
/*
* 析构函数
*/
Test::~Test()
{
}
//创建单实例对象
Test* Test::getInstance()
{
Test::m_sLock.mutexLock();
if(m_pTest == NULL)
{
m_pTest = new(std::nothrow) Test;
}
Test::m_sLock.mutexUnlock();
return m_pTest;
}
说明:
1.创建实例,getInstance()
2.不需要手动释放资源,通过静态对象m_sGarbo的析构函数自动释放
第三种:(推荐使用方式)
补充:
已初始化的的全局变量和局部静态变量存放在data段,
未初始化的全局变量和局部静态变量一般存放在bss段里,
前者在生产可执行文件时就分配好内存了,后者在加载时才分配。
注意:
所谓static对象指的是内存在data段和bss段中的对象.这类对象在整个程序的生命周期内都是存在的,除非程序结束否则会一直存在,
利用static关键字声明的对象是static对象中的一种,但是如果是在函数内部定义的static对象,那么这种static对象被称为local static对象,
除此之外的则是non-local static对象,比如:global作用域内的static对象,namespace作用域内的static对象,
类作用域内使用static关键字声明的对象,file作用域内的static对象等.
local static对象的一个特性就是不会在编译的时候自动初始化,然后在调用函数的时候碰到local static对象定义的时候才会对其进行初始化。
从另外一个角度来看,任何一种 non-const static 对象,不论是local 或者non-local ,在多线程环境下“等待某事发生”都会有麻烦。
处理这个麻烦的做法是:(1) 可以在程序的单线程启动阶段手工调用;(2) 也可以通过加锁的方式解决多线程的问题。
############# Test.h #############
class Test
{
public:
//获取单例指针
static Test* getInstance();
private:
Test();
~Test();
//防止拷贝和赋值, 只声明实现
Test(Test&);
Test& operator=(Test&);
};
############# Test.cpp #############
/*
* 构造函数
*/
Test::Test()
{
}
/*
* 析构函数
*/
Test::~Test()
{
}
/*
* 单实例实现
*/
Test* Test:getInstance()
{
static Test instance; //局部静态变量
return &instance;
}
说明:
1.使用单例getInstance()
2.不用资源释放问题
(2) 饿汉式
饿汉式的方式是一开始就加载了,以空间换时间,即定义一个静态的私有对象。不存在多线程问题。
class singleton
{
protected:
singleton()
{}
private:
static singleton* p;
public:
static singleton* initance();
};
singleton* singleton::p = new singleton;
singleton* singleton::initance()
{
return p;
}