## 模式定义
单例模式(Singleton Pattern):单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。
单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。单例模式是一种对象创建型模式。单例模式又名单件模式或单态模式。
## 模式动机
对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。
## 模式结构
单例模式包含如下角色:
- **Singleton**:单例

## C++单例的实现
### 基础要点
- **意图**: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
- **主要解决**: 一个全局使用的类频繁地创建与销毁。
- **何时使用**: 当您想控制实例数目,节省系统资源的时候。
- **如何解决**: 判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
- **关键代码**: 构造函数是私有的。
### 懒汉式
```cpp
//懒汉式的方法是直到使用时才实例化对象,也就说直到调用getInstance() 方法的时候才 new 一个单例的对象。好处是如果被调用就不会占用内存。
class Singleton_lazy {
private:
Singleton_lazy() {
cout << "Singleton_lazy() 构造函数被调用!" << endl;
}
public:
static Singleton_lazy* getInstance() { //公共接口
if (pSingleton == nullptr) {
pSingleton = new Singleton_lazy;
}
return pSingleton;
}
private:
static Singleton_lazy* pSingleton; //静态成员变量所有类共享一份
};
Singleton_lazy* Singleton_lazy::pSingleton = nullptr;
void test1() {
Singleton_lazy* s1 = Singleton_lazy::getInstance();
Singleton_lazy* s2 = Singleton_lazy::getInstance();
}
```
上述代码输出为
```
Singleton_lazy() 构造函数被调用!
```
由此可见,两次获取一个对象,只调用了一次构造函数,最基本的单例模式实现了。但是它是有哪些问题呢?
1. 线程安全的问题,当多线程获取单例时有可能引发竞态条件:第一个线程在 `if` 中判断 `pSingleton` 是空的,于是开始实例化单例;同时第 2 个线程也尝试获取单例,这个时候判断 `pSingleton` 还是空的,于是也开始实例化单例;这样就会实例化出两个对象,这就是线程安全问题的由来; **解决办法**:加锁
2. 内存泄漏. 注意到类中只负责 `new` 出对象,却没有负责 `delete` 对象,因此只有构造函数被调用,析构函数却没有被调用;因此会导致内存泄漏。**解决办法**: 使用共享指针,嵌套垃圾回收;
关于线程安全问题,加一把锁就能解决,此处不做过的多赘述,详情请见 [C++ 11 互斥量简介](https://neo00.top/archives/c11%E4%BA%92%E6%96%A5%E9%87%8F)。
其实就单例模式而言,回收内存意义不大,因为在所有程序中该对象只有一份,但是聊胜于无,既然想要回收那就不能显式调用 `delete` 去回收,因为有可能其他人在使用的时候不小心进行了释放,导致产生莫名的 `BUG`,所以设计嵌套类的目的是为了定义它的静态子对象,在程序结束时会调用该子对象的析构函数以释放唯一的实例。
#### 使用嵌套类 + 锁实现的单例模式
```cpp
class Singleton_lazy {
private:
Singleton_lazy() {
cout << "Singleton_lazy() 构造函数被调用!" << endl;
}
class Garbo { //嵌套类,它的唯一工作就是在析构函数中释放实例
public:
~Garbo() {
if (Singleton_lazy::pSingleton != nullptr) { //可以在这里销毁所有的资源
delete Singleton_lazy::pSingleton;
cout << "Singleton_lazy() 被释放!" << endl;
Singleton_lazy::pSingleton = nullptr;
}
}
};
public:
static Singleton_lazy* getInstance() {
//在判断前,使用锁来保证线程安全,该锁自动加锁,自动解锁
if(pSingleton == nullptr){ //双检锁
lock_guard<mutex> lk(m_mutex);
if (pSingleton == nullptr) {
pSingleton = new Singleton_lazy;
}
}
return pSingleton;
}
private:
static Singleton_lazy* pSingleton;
static Garbo garbo;//定义一个子对象,在程序结束时会调用它的析构函数
static mutex m_mutex;//定义一把锁,用来保证线程安全
};
Singleton_lazy* Singleton_lazy::pSingleton = nullptr;
Singleton_lazy::Garbo Singleton_lazy::garbo;
mutex Singleton_lazy::m_mutex;
void test1() {
Singleton_lazy* s1 = Singleton_lazy::getInstance();
Singleton_lazy* s2 = Singleton_lazy::getInstance();
}
```
#### 使用智能指针 + 锁实现的单例模式
也可以使用C++ 11 中的智能指针 `share_ptr` 进行解决,当智能指针在被释放会自动释放自己占据的内存,请见[智能指针](https://neo00.top/archives/c11%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88)。
```cpp
class Singleton_lazy {
private:
Singleton_lazy() {
cout << "Singleton_lazy() 构造函数被调用!" << endl;
}
public:
using Ptr = shared_ptr<Singleton_lazy>; //使用智能指针
static Ptr getInstance() { //第一处更改,返回智能指针
if(pSingleton == nullptr){ //双检锁,避免重复加锁
lock_guard<mutex> lk(m_mutex);
if (pSingleton == nullptr) {
pSingleton = shared_ptr<Singleton_lazy>(new Singleton_lazy); //第二处更改,创建智能指针对象
}
}
return pSingleton;
}
private:
static Ptr pSingleton; //第三处更改
static mutex m_mutex;
};
Singleton_lazy::Ptr Singleton_lazy::pSingleton = nullptr; //第四处
mutex Singleton_lazy::m_mutex;
void test1() {
Singleton_lazy::Ptr s1 = Singleton_lazy::getInstance(); //第五处
Singleton_lazy::Ptr s2 = Singleton_lazy::getInstance();
}
```
`shared_ptr` 和 `mutex` 都是 C++ 11的标准,以上这种方法的优点是:
1. 基于 shared_ptr, 用了C++比较倡导的 RAII思想,用对象管理资源,当 shared_ptr 析构的时候,new 出来的对象也会被 delete掉。以此避免内存泄漏
2. 加了锁,使用互斥量来达到线程安全。这里使用了两个 if判断语句的技术称为双检锁;好处是,只有判断指针为空的时候才加锁,避免每次调用 get_instance的方法都加锁,锁的开销毕竟还是有点大的。
不足之处在于: 使用智能指针会要求用户也得使用智能指针,非必要不应该提出这种约束; 使用锁也有开销; 同时代码量也增多了,实现上我们希望越简单越好。
因此这里还有第三种的基于 Magic Staic的方法达到线程安全
#### 基于局部静态变量的单例模式(magic static )
```cpp
class Singleton_lazy {
private:
Singleton_lazy() {
cout << "Singleton_lazy() 构造函数被调用!" << endl;
}
public:
static Singleton_lazy& getInstance() {
static Singleton_lazy pSingleton;
return pSingleton;
}
~Singleton_lazy() {
cout << "Singleton_lazy() 析构函数被调用!" << endl;
}
private:
};
void test1() {
Singleton_lazy& s1 = Singleton_lazy::getInstance();
Singleton_lazy& s2 = Singleton_lazy::getInstance();
}
```
上述代码输出为
```
Singleton_lazy() 构造函数被调用!
Singleton_lazy() 析构函数被调用!
```
这样保证了并发线程在获取静态局部变量的时候一定是初始化过的,所以具有线程安全性。C++静态变量的生存期 是从声明到程序结束,这也是一种懒汉式。
**这是最推荐的一种单例实现方式:**
1. 通过局部静态变量的特性保证了线程安全 (C++11, GCC > 4.3, VS2015支持该特性);
2. 不需要使用共享指针,代码简洁,不需要担心内存释放的问题;
3. 注意在使用的时候需要声明单例的引用 `Singleton_lazy&` 才能获取对象。
### 饿汉式
饿汉式是一个线程安全的写法,但是由于先初始化,所以会产生垃圾对象。
优点:没有加锁这一步,执行效率提高
缺点:在代码执行前就加载好了,浪费内存
比较简单也没什么好说的。。。
```cpp
class Singleton_hungry {
private:
Singleton_hungry() {}
static Singleton_hungry* getInstance() { return pSingleton; }
private:
static Singleton_hungry* pSingleton;
};
Singleton_hungry* Singleton_hungry::pSingleton = new Singleton_hungry;
```
[C++ 单例模式总结与剖析](https://www.cnblogs.com/sunchaothu/p/10389842.html)
创建型模式之单例模式