单例模式
一、概述
这里记录一下单例模式的最常用使用,单例模式(Single Pattern)是一种常用的软件设计模式,它属于创建型模式。单例模式的定义是确保一个类仅有一个实例,并提供一个全局访问点来获取这个唯一的实例。该模式的核心在于控制实例的数目,使得在整个系统中,该类只被实例化一次。
1. 特点
- 单例类只能有一个实例:这是单例模式最基本的要求,确保类的全局唯一性。
- 单例类必须自行创建自己的唯一实例:这通常通过私有化构造函数来实现,防止外部通过new关键字直接创建实例。
- 单例类必须给所有其他对象提供这一实例:这通常通过一个静态的公有方法来实现,该方法负责返回类的唯一实例。
2. 实现方式
单例模式有多种实现方式,其中最典型的是懒汉式和饿汉式。
懒汉式:在真正需要使用对象时才去创建该单例类对象。这种方式在类加载时不会立即创建实例,而是在首次调用getInstance()方法时才创建,并通过加锁(如synchronized关键字)来保证多线程环境下的线程安全。但这种方式在多线程环境下存在性能问题,因为每次调用getInstance()方法时都需要进行同步判断。
饿汉式:在类加载时已经创建好该单例对象,等待程序使用。这种方式因为实例在类加载时就已经创建好了,所以不需要进行同步判断,是线程安全的。但这种方式在类加载时就占用了内存资源,如果单例对象体积较大或者类加载顺序不确定时,可能会浪费内存资源。
除了懒汉式和饿汉式外,还有枚举式、双重校验锁式、静态内部类式等多种单例模式的实现方式。这些方式各有优缺点,可以根据实际需求和场景选择适合的实现方式。
3. 应用场景
单例模式适用于以下场景:
- 全局唯一性:当需要控制某个类的实例数目为1时,可以使用单例模式。
- 共享资源:当多个对象需要共享一个资源时,可以将该资源设计为单例模式,以避免资源的重复创建和浪费。
- 配置信息:如应用程序的配置信息、全局缓存等,可以设计为单例模式,以便于全局访问和管理。
二、实现代码
1. 静态局部变量的懒汉单例
这个是线程安全的,因为静态局部变量的创建方式天然是线程安全的,不存在线程不安全的问题,我基本上只用这个
class Single {
public:
static Single& GetInstance(); // 获取单实例对象
void Print(); // 打印实例地址
private:
Single(); // 禁止外部构造
~Single(); // 禁止外部析构
Single(const Single &single) = delete; // 禁止外部拷贝构造
Single(const Single &&) = delete; // 禁止右值拷贝构造
const Single &operator=(const Single &single) = delete; // 禁止外部赋值操作
};
Single& Single::GetInstance(){
/**
* 静态局部变量只在当前函数内有效,其他函数无法访问。
* 静态局部变量只在第一次被调用的时候初始化,也存储在静态存储区,生命周期从第一次被初始化起至程序结束止。
*/
static Single single;
return single;
}
void Single::Print(){
std::cout << "实例内存地址:" << this << std::endl;
}
Single::Single() {
std::cout << "构造函数" << std::endl;
}
Single::~Single() {
std::cout << "析构函数" << std::endl;
}
2. 加锁的懒汉式单例
加锁的懒汉式实现
class Single {
public:
static Single *GetInstance(); // 获取单实例对象
static void deleteInstance(); //释放单实例,进程退出时调用
void Print(); // 打印实例地址
private:
Single(); // 将其构造和析构成为私有的, 禁止外部构造和析构
~Single();
Single(const Single &signal); // 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝、赋值、右值拷贝构造
Single(const Single &&) = delete;
const Single &operator=(const Single &signal);
private:
static Single *m_Single; // 实例对象指针
static std::mutex m_Mutex; // 互斥锁
};
//初始化静态成员变量
Single *Single::m_Single = nullptr;
std::mutex Single::m_Mutex;
// 注意:不能返回指针的引用,否则存在外部被修改的风险!
Single * Single::GetInstance(){
// 这里使用了两个 if 判断语句的技术称为双检锁;好处是,只有判断指针为空的时候才加锁,
// 避免每次调用 GetInstance的方法都加锁,锁的开销毕竟还是有点大的。
if (m_Single == nullptr){
std::unique_lock<std::mutex> lock(m_Mutex); // 加锁
if (m_Single == nullptr){
volatile auto temp = new (std::nothrow) Single();
m_Single = temp;
}
}
return m_Single;
}
void Single::deleteInstance(){
std::unique_lock<std::mutex> lock(m_Mutex); // 加锁
if (m_Single){
delete m_Single;
m_Single = nullptr;
}
}
void Single::Print(){
std::cout << "实例内存地址:" << this << std::endl;
}
Single::Single(){
std::cout << "构造函数" << std::endl;
}
Single::~Single(){
std::cout << "析构函数" << std::endl;
}
使用智能指针
#include <iostream>
#include <memory>
#include <mutex>
class Single {
public:
static std::shared_ptr<Single> GetInstance();
void print() {
std::cout << "Hello World." << std::endl;
}
~Single() {
std::cout << "析构函数" << std::endl;
}
private:
Single() {
std::cout << "构造函数" << std::endl;
}
};
static std::shared_ptr<Single> Single = nullptr;
static std::mutex SingleMutex;
std::shared_ptr<Single> Single::GetInstance() {
if (Single == nullptr) {
std::unique_lock<std::mutex> lock(SingleMutex);
if (Single == nullptr) {
volatile auto temp = std::shared_ptr<Single>(new Single());
Single = temp;
}
}
return Single;
}
3. 使用 C++11 中的 std::call_one 的懒汉单例
#include <iostream>
#include <memory>
#include <mutex>
class Single {
public:
static std::shared_ptr<Single> GetInstance();
void print() {
std::cout << "Hello World." << std::endl;
}
~Single() {
std::cout << "析构函数" << std::endl;
}
private:
Single() {
std::cout << "构造函数" << std::endl;
}
};
static std::shared_ptr<Single> Single = nullptr;
static std::once_flag SingleFlag;
std::shared_ptr<Single> Single::GetInstance() {
std::call_once(SingleFlag, [&] {
Single = std::shared_ptr<Single>(new Single());
});
return Single;
}
4. 饿汉式单例
class Singleton{
public:
static Singleton* GetInstance(); // 获取单实例
static void deleteInstance(); // 释放单实例,进程退出时调用
void Print(); // 打印实例地址
private:
Singleton(); // 将其构造和析构成为私有的, 禁止外部构造和析构
~Singleton();
Singleton(const Singleton &signal); // 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值
Single(const Single &&) = delete;
const Singleton &operator=(const Singleton &signal);
private:
static Singleton *m_pSingleton; // 单例指针
};
// 代码一运行就初始化创建实例 ,本身就线程安全
Singleton* Singleton::m_pSingleton = new (std::nothrow) Singleton();
Singleton* Singleton::GetInstance(){
return m_pSingleton;
}
void Singleton::deleteInstance(){
if (m_pSingleton) {
delete m_pSingleton;
m_pSingleton = nullptr;
}
}
void Singleton::Print(){
std::cout << "实例内存地址:" << this << std::endl;
}
Singleton::Singleton(){
std::cout << "构造函数" << std::endl;
}
Singleton::~Singleton(){
std::cout << "析构函数" << std::endl;
}