C++实现单例模式
解释:顾名思义,单例模式就是只有一个实例。在有些设计过程中,我们只希望生成一个模式,比如网页中的对话框、聊天界面(每个联系人只能打开一个聊天界面),内存池等,如果出现多个实例,则会造成严重的后果。
一、简单实现:
class Singleton
{
public:
static Singleton*getinstance();
~Singleton();
private:
Singleton();//私有构造函数
Singleton(const Singleton &);//复制构造函数
static Singleton *instance;
}
Singleton::Singleton(){}
Singleton::Singleton(const Singleton&){}
Singleton*Singleton::getinstance()
{
if(instance == NULL)
{
instance = new Singleton;
}
return instance;
}
Singleton::~Singleton(){}
有几点需要注意:
1)一般的构造函数都是public的,但是此处的构造函数声明为了私有,这里理解应该为是单例模式的核心所在,将构造函数声明为私有,则外在的程序就不能使用new来实例化,这就是让类自身负责保存他唯一的实例,这个类可以保障没有其他实例可以被创建。
2)由于外部的代码不能实例化,我们写了一个public的函数getinstance,该函数的目的就是返回一个实例。
3)此为懒汉模式,懒汉模式是加载时没有创建实例,在获取对象的时候创建,因此加载速度快,但是获取对象慢。懒汉模式有个致命问题,在多线程的时候不安全,因为若有多个线程同时运行到此处,则会创建多个实例,这也就失去了单例模式的意义。可以通过加锁来解决此问题。
二、多线程安全的单例模式
实现1:懒汉加锁模式
class Singleton
{
protected:
Singleton()
{
pthread_mutex_init(&mutex);
}
private:
Singleton();//私有构造函数
Singleton(const Singleton &);//复制构造函数
static Singleton*p;
public:
static pthread_mutex_t mutex;
static Singleton*instance;
}
pthread_mutex_t singleton::mutex;
Singleton::Singleton(){}
Singleton::Singleton(const Singleton &){}
singleton* singleton::p = NULL;
singleton* singleton::initance()
{
if (p == NULL)
{
pthread_mutex_lock(&mutex);
if (p == NULL)
p = new singleton();
pthread_mutex_unlock(&mutex);
}
return p;
}
注意:
1)加锁是确保一个线程进入临界区,另一个线程进不去。当一个线程进入锁定的代码,另一个就会一直等待,直到线程被释放。
2)此处有两个判断,被称为双重锁定。因为若有两个线程同时执行到这里的时候,他们都会通过第一层判断,但是由于加锁的原因,第一个进去创建了实例并且出来之后,第二个才被允许进入,若不进行第二项为空的判断,则第二个线程又会创建一个实例,则单例模式失效。
实现2:懒汉静态变量模式。
class Singleton
{
private:
Singleton();
public:
static Singleton *instance();
static pthread_mutex_t mutex;
}
pthread_mutex_t Singleton::mutex;//此处一定要定义,由于是静态变量,必须要在外部定义和初始化
Singleton::Singleton()
{
pthread_mutex_init(&mutex);
}
Singleton *Singleton::instance()
{
pthread_mutex_lock(&mutex);
static Singleton obj;
pthread_mutex_unlock(&mutex);
return &obj;
}
这里简单介绍一下静态变量:
当我们每创建一个类对象,类成员变量就会多出一份,各个对象间的成员变量不能共享,是各自独立的变量。但是,我们有些时候需要定义一个成员变量,使其在各个类对象之间关联起来。也就是说这个变量是所有的类共有,这就是静态变量。静态变量的特性和全局变量特性相同,生命周期为程序运行期,不依赖于任何实例的对象。
引申静态成员函数:
静态成员函数属于整个类所有,因为静态成员函数并不属于某个对象,所以我们可以通过类名来直接访问公有静态成员函数。也可以通过对象名访问公有成员函数。由于构造函数为私有,所以不可以通过外部函数来获取实例,只能通过静态函数来获取单例。
所以上面函数,当有多个实例调用instance的时候,返回的都是同一个实例obj。
三、 多线程安全的饿汉模式
代码实现:
class Singleton
{
private:
Singleton();
Singleton(const Singleton&);
static Singleton * p;
public:
Singleton * instance();
}
Singleton::Singleton(){}
Singleton::Singleton(const Singleton&){}//复制构造函数
Singleton *Singleton::p=new Singleton;
Singleton *Singleton::instance()
{
return p;
}
注意:
1)饿汉模式,顾名思义,需要提前喂食,也即是提前加载。采用静态初始化的方式,提前加载实例化对象,但是由于提前加载,也就提前占用了系统资源,导致内存开销大。
2)此处没有加锁为什么也是线程安全的?因为程序在刚加载的时候就创建了实例,并且返回他,以后如果再次运行到这里,返回的就是之前加载时创建的实例,所以不会导致多线程安全问题。
3)类指针要声明为静态的,否则不可以在类外进行赋值操作。