单例模式
单例模式,是一种类的设计模式,采用单例模式设计的类,只允许有一个实例。
为什么要采用单例模式:
1、有些对象在程序的整个声明周期中,为了保证数据的正确性,只允许有一个实例。
2、节省资源
3、满足“低耦合”的设计
单例模式的实现:
单例模式有两种实现方式:懒汉模式、饿汉模式
#if 0 //懒汉模式
#include<iostream>
#include<mutex>
#include<Windows.h>
using namespace std;
#define __MemoryBarrier__
class Singleton {
public:
static Singleton* GetInstance()
{
//thread1
//thread2
if (_sInstance == NULL) //双重检查是为了效率
{
//RAII
//_mtx.lock(); //线程安全
lock_guard<mutex> lock(_mtx); //在构造中加锁,在析构中解锁。出了这个作用域自动解锁
if (_sInstance == NULL)
{
_sInstance = new Singleton; //有可能会抛异常,可能会死锁
//1、可以在下面捕获异常
//2、利用RAII 的方式,可以在构造中加锁,在析构中解锁
//3、使用lock_guard,c++11,构造函数中lock,出了作用域在析构中解锁
//如果带有赋值的话,还需要防止编译器优化,需要设定一个内存栅栏
#ifndef __MemoryBarrier__
Singleton* tmp = new Singleton();
MemoryBarrier(); //内存栅栏
_sInstance = tmp;
#endif //
}
//_mtx.unlock();
}
return _sInstance;
}
void Print()
{
std::cout << _data << std::endl;
}
private:
Singleton() //构造私有
: _data(0)
{}
//防止拷贝
Singleton(Singleton&);
Singleton& operator=(Singleton&);
static Singleton* _sInstance;
static mutex _mtx;
int _data;
};
Singleton* Singleton::_sInstance = NULL;
mutex Singleton::_mtx;
int main()
{
Singleton::GetInstance()->Print();
system("pause");
return 0;
}
#endif
- 首先关于双重 if 条件判断,这个操作是为了节省时间,提高效率。因为获取锁资源,加锁、解锁这个过程本身就是比较耗费时间的。
- 单例模式为了保证数据的正确性,保证了在整个声明周期中只有一个实例,那么我们就需要把构造函数、赋值拷贝函数、等号运算符重载函数全部放到类的私有域中,或者用c++11的方法用delete 关键词删掉就好,如果用delete的话,构造函数不能删,仍然留在类的私有域中
- 为了满足RAII 的方式,在加锁解锁的时候,我们采用c++11 中的 lock_guard ,这个模板类。在其作用域中,他会自己调用构造函数进行加锁,出了这个作用域它会自己调用析构函数进行解锁。
- 当我们使用 new 的时候,我们就需要注意 new 不成功的状况,当new 失败的时候它会抛出一个异常,也就是空间申请不成功。如果程序跑出锁这个作用域的时候,就可能会造成死锁。因此我们需要对异常的情况进行处理。
- 如果我们采用将申请的空间采用赋值的方式给我所定义的对象的时候,这个过程中涉及三个步骤,申请空间,对这块空间进行构造调用构造函数,赋值。那么这时编译器可能会去优化,将指令的顺序打乱,可能先去申请空间,然后在赋值,在构造。这个时候就可能会发生意想不到的事情。因此我们引入一个内存栅栏的概念,让编译器严格按照指令顺序去运行。
#if 0 //饿汉模式
#include<iostream>
#include<mutex>
#include<assert.h>
using namespace std;
class Singleton {
public:
static Singleton* GetInstance()
{
//assert(_sInstance);
static Singleton tmp; //也可以这样
return &tmp;
//return _sInstance;
}
void Print()
{
std::cout << _data << std::endl;
}
private:
Singleton() //构造私有
: _data(0)
{}
//防止拷贝
Singleton(Singleton&);
Singleton& operator=(Singleton&);
static Singleton* _sInstance;
int _data;
};
Singleton* Singleton::_sInstance = new Singleton;
int main()
{
Singleton::GetInstance()->Print();
system("pause");
return 0;
}
#endif
- 饿汉模式其实大体的思路是和懒汉模式是一样的,是在程序运行前已经将我的对象指针构造好。
- main函数以前就初始化了。因为main函数以前只有一个线程,因此不存在线程安全的问题
- 但是还是会出现一些问题,在静态库动态库会出现问题,比如我在库当中也有静态的东西,需要在main 函数之前去调用,并且构造函数时就创建一个线程还是会出现问题的。此类问题防不胜防。因此我们最好的是我们程序员自己注意。