目录
一、介绍
单例模式(Singleton Pattern):这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
二、应用场景
1、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
2、一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。
一些设备管理器常常设计为单例模式。
三、要点
单例模式要点有如下三点:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
从实现角度即以下三点:
- 单例模式的类只提供私有的构造函数。
- 类定义中含有一个该类的静态私有对象。
- 该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。
同时还要注意:1、线程安全(双检锁DCL:double-checked-lock)。2、资源释放。
四、样例Demo
4.1、局部静态变量
这种方式很常见,实现简单,且无需担心单例的销毁问题。
// singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H
// 非真正意义上的单例
class Singleton
{
public:
static Singleton& GetInstance()
{
static Singleton instance;
return instance;
}
private:
Singleton() {}
};
但这并非真正意义上的单例,当使用如下方式访问单例时:
Singleton single = Singleton::GetInstance();
因为编译器会生成一个默认的拷贝构造函数,这时会出现一个类拷贝问题,违背了单例特性。要避免该问题,可以有如下两种方法:
1、将GetInstance函数的返回类型修改为指针,而非引用。
// singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H
// 单例
class Singleton
{
public:
// 修改返回类型为指针类型
static Singleton* GetInstance()
{
static Singleton instance;
return &instance;
}
private:
Singleton() {}
};
#endif // SINGLETON_H
2、显示的声明类的拷贝构造函数并重载赋值运算符。
// singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H
#include <iostream>
using namespace std;
// 单例
class Singleton
{
public:
static Singleton& GetInstance()
{
static Singleton instance;
return instance;
}
void doSomething() {
cout << "Do something" << endl;
}
private:
Singleton() {} // 构造函数(被保护)
Singleton(Singleton const &); // 无需实现
Singleton& operator = (const Singleton &); // 无需实现
};
#endif // SINGLETON_H
这样就可以保证只有一个实例,又不用担心内存回收问题。
Singleton::GetInstance().doSomething(); // OK
Singleton single = Singleton::GetInstance(); // Error 不能编译通过
4.2、懒汉式与饿汉式
先看Singleton的头文件,懒汉与饿汉式公用部分:
// singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H
// 单例 - 懒汉式/饿汉式公用
class Singleton
{
public:
static Singleton* GetInstance();
private:
Singleton() {} // 构造函数(被保护)
private:
static Singleton *m_pSingleton; // 指向单例对象的指针
};
#endif // SINGLETON_H
懒汉式
特点:Lazy初始化;非多线程安全。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁才能保证单例,但加锁会影响效率。
// singleton.cpp
#include "singleton.h"
// 单例 - 懒汉式
Singleton *Singleton::m_pSingleton = NULL;
Singleton *Singleton::GetInstance()
{
if (m_pSingleton == NULL)
m_pSingleton = new Singleton();
return m_pSingleton;
}
饿汉式
特点:非Lazy初始化;多线程安全。
优点:没有加锁,执行效率提高。
缺点:类加载时就初始化,浪费内存。
// singleton.cpp
#include "singleton.h"
// 单例 - 饿汉式
Singleton *Singleton::m_pSingleton = new Singleton();
Singleton *Singleton::GetInstance()
{
return m_pSingleton;
}
4.3、线程安全
在懒汉式下有非多线程安全问题,引入DCL机制可以解决。
singleton.h内容:
// singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H
#include <iostream>
#include <mutex>
using namespace std;
// 单例 - 懒汉式/饿汉式公用
class Singleton
{
public:
static Singleton* GetInstance();
private:
Singleton() {} // 构造函数(被保护)
private:
static Singleton *m_pSingleton; // 指向单例对象的指针
static mutex m_mutex; // 锁
};
#endif // SINGLETON_H
singleton.cpp内容:
// singleton.cpp
#include "singleton.h"
// 单例 - 懒汉式(双检锁 DCL 机制)
Singleton *Singleton::m_pSingleton = NULL;
mutex Singleton::m_mutex;
Singleton *Singleton::GetInstance()
{
if (m_pSingleton == NULL) {
std::lock_guard<std::mutex> lock(m_mutex); // 自解锁
if (m_pSingleton == NULL) {
m_pSingleton = new Singleton();
}
}
return m_pSingleton;
}
这样可以保证线程安全,只是会带来一点性能影响。
4.4、资源释放
有内存申请,就有内存释放,可以采用如下方式:
1、主动释放(手动调用接口释放内存)。
2、自动释放(程序自己释放)。
要手动释放资源,添加一个static接口,编写需要释放资源的代码。
// 单例 - 主动释放
static void DestoryInstance()
{
if (m_pSingleton != NULL) {
delete m_pSingleton;
m_pSingleton = NULL;
}
}
// 需要释放资源的时候再手动调用
Singleton::GetInstance()->DestoryInstance();
方式虽然简单,但很多时候,容易忘记调用DestoryInstance,这时可以采用更简便方法。
// singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H
#include <iostream>
using namespace std;
// 单例 - 自动释放
class Singleton
{
public:
static Singleton* GetInstance();
private:
Singleton() {} // 构造函数(被保护)
private:
static Singleton *m_pSingleton; // 指向单例对象的指针
// GC 机制
class GC
{
public:
~GC()
{
// 可以在这里销毁所有的资源,例如:db 连接、文件句柄等
if (m_pSingleton != NULL) {
cout << "Here destroy the m_pSingleton..." << endl;
delete m_pSingleton;
m_pSingleton = NULL;
}
}
static GC gc; // 用于释放单例
};
};
#endif // SINGLETON_H
只需要声明Sinleton::GC即可:
// main.cpp
#include "singleton.h"
Singleton::GC Singleton::GC::gc; // 重要
int main()
{
Singleton *pSingleton1 = Singleton::GetInstance();
Singleton *pSingleton2 = Singleton::GetInstance();
cout << (pSingleton1 == pSingleton2) << endl;
return 0;
}
程序运行结束时,系统会调用Singleton的静态成员GC的析构函数,该析构函数会进行资源的释放。这种方式最大优点就是在“不知不觉”中进行,所以对开发者来说尤为省心。
五、优缺点
优点
1、实例控制:单例模式会阻止其他对象实例化自己的单例对象的副本,从而确保所有对象都访问唯一实例,避免对资源的多重利用。
2、灵活性:因为类控制了实例化过程,所以类可以灵活更改实例化过程。
缺点:
1、开销:虽然数量少,但如果每次对象请求都要检查是否存在类实例,将仍然需要一些开销。可以通过使用静态初始化解决(有的也将开销少划为优点,应该是当正确使用时为优点。)。
2、可能的开发混淆:使用单例对象时,开发人员必须记住不能使用new关键字实例化此对象。因为可能无法访问库源码,开发人员可能会意外发现无法直接实例化此类。
3、对象生存期:不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言),只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用。