单例模式:
单例大约有两种实现方法:懒汉与饿汉。
懒汉:故名思义,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化,
饿汉:饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。
(1)饿汉
饿汉单例,即在最开始的时候,静态对象就已经创建完成;
设计方法是类中包含一个静态成员指针,该指针指向该类的一个对象,提供一个公有的静态成员方法,返回该对象指针;为了使得对象唯一,还需要将构造函数设为私有,代码如下:
class Sigletion
{
//构造函数为私有;
Sigletion()
{
cout << "Sigletion()" <<endl;
}
static Sigletion * intance; //静态成员,指向Sigletion对象的指针。
public:
//提供静态共有方法,可以使用类名加域名进行访问,返回对象指针;
static Sigletion* GetSigletion()
{
return intance;
}
};
Sigletion* Sigletion:: intance = new Sigletion;
测试代码:
int main()
{
/*我们注意到,在刚进入main函数,我们就打印了一句“”main begin“”,为什么要在这里加入一句输出语句呢?
因为我们此时的单例模式采用的是饿汉单例,所以,哪怕你还没用到我这个对象,我也会先创建一个出来,
先占着,即所谓的饿汉,其实就是类似于全局变量的构造是在进入main函数之前的原理,*/
cout << "main begin" <<endl; //验证静态对象的创建在main之前。
//验证无法创建实例。
//Sigletion tmp;
//Sigletion *ptr = new Sigletion;
//调用共有的静态成员方法
Sigletion *ptr = Sigletion::GetSigletion();
Sigletion *ptr2 = Sigletion::GetSigletion();
if (ptr == ptr2)
{
cout << "yes" <<endl;
}
else
{
cout << "no"<<endl;
}
cout << "hello..." <<endl;
system("pause");
return 0;
}
单例的饿汉实现是线程安全的,因为对象在使用前就已经创建出来了
(2)懒汉:
所谓懒汉模式,就是尽可能晚的创建这个对象的实例,即在单例类第一次被引用时将自己初始化;其实C++里很多地方都是类似这样的思想,比如晚绑定,写时拷贝技术等,就是尽量使资源的利用最大化,不要让空闲的人还占着有限的资源。
class Sigletion2
{
Sigletion2()
{
cout << "Sigletion2()" <<endl;
}
static Sigletion2* intance2;
public:
static Sigletion2* GetSigletion2()
{
if (intance2 == NULL)
{
intance2 = new Sigletion2();
cout << "it is once" <<endl;
}
else
{
cout << "it is not once" <<endl;
}
return intance2;
}
};
Sigletion2* Sigletion2:: intance2 = NULL; //先初始化为空,等真正用上这个单例的时候再创建这个例。
(3)懒汉的线程安全问题:
如果此时多线程进行操作,简单点以两个线程为例,假设pthread_1刚判断完 intance 为NULL 为真,准备创建实例的时候,切换到了pthread_2, 此时pthread_2也判断intance为NULL为真,创建了一个实例,再切回pthread_1的时候继续创建一个实例返回,那么此时就不再满足单例模式的要求了, 既然这样,是因为多线程访问出的问题,那我们就来加把锁,使得线程同步;
class singleton
{
private:
singleton()
{
pthread_mutex_init(&mutex);
}
static singleton* p;
static pthread_mutex_t mutex;
public:
static singleton* initance()
{
pthread_mutex_lock(&mutex);
if (p == NULL)
{
p = new singleton();
}
pthread_mutex_unlock(&mutex);
return p;
}
};
pthread_mutex_t singleton::mutex;
singleton* singleton::p = NULL;
那么我们这样写的代码是没有问题的,但是效率可能有点低,因为加锁是一个非常耗时的操作,没有必要每次都要加锁解锁,只有刚开始创建对象的时候需要加锁。如果对象都
创建出来了,就没必要加锁解锁了,直接返回这个对象的指针。
class singleton
{
private:
singleton()
{
pthread_mutex_init(&mutex);
}
static singleton* p;
static pthread_mutex_t mutex;
public:
static singleton* initance()
{
if (p == NULL) //p != NULL,说明对象已经创建出来了,直接返回对象的指针,没必要在加锁解锁浪费时间。
{
pthread_mutex_lock(&mutex);
if (p == NULL)
{
p = new singleton();
}
pthread_mutex_unlock(&mutex);
}
return p;
}
};
pthread_mutex_t singleton::mutex;
singleton* singleton::p = NULL;
允许可变数目的实例,基于单例模式我们可以扩展,使用与单例控制相似的方法来获得指定个数的对象实例。(即单例类内有多个静态对象指针成员,每次当单例类被引用时随机分配一个实例对象);
单例模式的适用场景
(1)系统只需要一个实例对象,或者考虑到资源消耗的太大而只允许创建一个对象。
(2)客户调用类的单个实例只允许使用一个公共访问点,除了该访问点之外不允许通过其它方式访问该实例 (就是共有的静态方法)。