目录
什么是单例模式?
在程序运行过程中,一个类只能实例化一个对象
为什么用单例模式?
有些模块在一个程序中只能存在一个实例,例如像操作系统中的任务管理器,如果不使用单例模式
需要考虑以下三个问题:
- 多个任务管理器是否有意义?因为任务管理器的内容是一样的
- 如果创建多个,那两个窗口的内容是否一致?
- 如果不一致,会产生误导,如果一致,以哪个为标准?
这种场景就没有必要创建多个类的实例化对象
如何实现单例模式?
要点
- 构造、拷贝构造、析构私有化,防止类外直接创建对象并初始化
- 通过静态指针获取对象实例,使用指针的原因是指针在32位平台下固定占4字节与类型无关,如果使用对象,类中成员变量较多时占用空间很大
- 提供公有的获取对象实例的方法,并且不能依赖对象调用,所以需要使用静态函数,通过类名+作用域直接调用
- 提供手动释放对象空间的方法,防止类内其它申请在堆区空间的成员产生内存泄露
饿汉式
类产生的时候就创建好实例对象,用空间换时间,实现简单,不用考虑线程安全的问题
上代码
#include<iostream>
using namespace std;
//饿汉
class Singleton
{
private:
Singleton(){cout<<"Singleton::Singleton()"<<endl;}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
~Singleton(){cout<<"Singleton::~Singleton()"<<endl;}
//实例化对象指针
static Singleton* m_instance;
public:
static Singleton* GetInstance()
{
if(m_instance == nullptr)
m_instance = new Singleton;
return m_instance;
}
//手动释放空间的函数
static void DestroyInstance()
{
if(m_instance != nullptr)
{
delete m_instance;
m_instance = nullptr;
}
}
};
Singleton* Singleton::m_instance = new Singleton();
int main()
{
system("pause");
return 0;
}
输出结果
如我们所想的一样,饿汉式单例模式加载程序时,类已经创建好实例对象,无需调用
修改main函数中的代码
int main()
{
Singleton* m_instance1 = Singleton::GetInstance();
Singleton* m_instance2 = Singleton::GetInstance();
system("pause");
return 0;
}
输出结果
多次获取对象时,类中只有一个实例对象
调用释放空间函数
int main()
{
Singleton* m_instance1 = Singleton::GetInstance();
Singleton* m_instance2 = Singleton::GetInstance();
Singleton::DestroyInstance();
system("pause");
return 0;
}
能够正确调用析构函数,释放内存空间
懒汉式
调用获取对象实例的函数才创建对象实例,即按需创建,适用于对资源要求十分严格的场景
#include<iostream>
using namespace std;
class CSingleton
{
private:
CSingleton(){cout<<"普通构造:CSingleton::CSingleton()"<<endl;}
~CSingleton(){cout<<"析构函数:CSingleton::~CSingleton()"<<endl;}
CSingleton(const CSingleton& single){cout<<"拷贝构造:CSingleton::CSingleton(CSingleton& single)"<<endl;}
CSingleton& operator = (const CSingleton& single){cout<<"=赋值运算符:CSingleton::operator=()"<<endl;}
static CSingleton* m_pInstance;
public:
static CSingleton* GetInstance()
{
if(!m_pInstance)
{
m_pInstance = new CSingleton;
}
return m_pInstance;
}
};
CSingleton* CSingleton::m_pInstance = nullptr;
int main()
{
system("pause");
return 0;
}
输出结果
可以看出,与饿汉式不同,懒汉式初始化时将静态指针赋值为空,并没有在程序运行加载类时实例化对象
修改main函数
int main()
{
CSingleton* single = CSingleton::GetInstance();
CSingleton* single2 = CSingleton::GetInstance();
if(single == single2)
{
cout<<&(*single)<<" "<<&(*single2)<<endl;
cout<<"the same ptr"<<endl;
}
system("pause");
return 0;
}
输出结果
两次调用GetInstance方法,两个实例化对象指针指向同一个地址空间
线程安全问题
#include<iostream>
#include<mutex>
#include<thread>
using namespace std;
class CSingleton
{
private:
CSingleton(){cout<<"普通构造:CSingleton::CSingleton()"<<endl;}
~CSingleton(){cout<<"析构函数:CSingleton::~CSingleton()"<<endl;}
CSingleton(const CSingleton& single){cout<<"拷贝构造:CSingleton::CSingleton(CSingleton& single)"<<endl;}
CSingleton& operator = (const CSingleton& single){cout<<"=赋值运算符:CSingleton::operator=()"<<endl;}
static CSingleton* m_pInstance;
public:
static CSingleton* GetInstance()
{
if(!m_pInstance)
{
m_pInstance = new CSingleton;
}
return m_pInstance;
}
};
CSingleton* CSingleton::m_pInstance = nullptr;
void f1()
{
CSingleton* single1 = CSingleton::GetInstance();
}
void f2()
{
CSingleton* single2 = CSingleton::GetInstance();
}
int main()
{
std::thread t1(f1);
std::thread t2(f2);
t2.join();
t1.join();
system("pause");
return 0;
}
输出结果
与饿汉式不同的是,我们的实例化对象并不是在加载类时直接创建的,而是通过手动调用GetInstance函数创建
在多线程环境下,假设单例还未初始化,有两个线程同时调用GetInstance方法,然后两个线程都初始化一个单例,调用了两次构造函数
不符合单例的条件,所以懒汉式的写法会出现线程安全的问题!
加锁
#include<iostream>
#include<mutex>
#include<thread>
using namespace std;
std::mutex mtx;
class CSingleton
{
private:
CSingleton(){cout<<"普通构造:CSingleton::CSingleton()"<<endl;}
~CSingleton(){cout<<"析构函数:CSingleton::~CSingleton()"<<endl;}
CSingleton(const CSingleton& single){cout<<"拷贝构造:CSingleton::CSingleton(CSingleton& single)"<<endl;}
CSingleton& operator = (const CSingleton& single){cout<<"=赋值运算符:CSingleton::operator=()"<<endl;}
static CSingleton* m_pInstance;
public:
static CSingleton* GetInstance()
{
mtx.lock();
if(!m_pInstance)
{
m_pInstance = new CSingleton;
}
mtx.unlock();
return m_pInstance;
}
};
CSingleton* CSingleton::m_pInstance = nullptr;
void f1()
{
CSingleton* single1 = CSingleton::GetInstance();
}
void f2()
{
CSingleton* single2 = CSingleton::GetInstance();
}
int main()
{
std::thread t1(f1);
std::thread t2(f2);
t2.join();
t1.join();
system("pause");
return 0;
}
输出结果
使用互斥锁加锁后,同一时间只有一个线程能获得锁,进行实例化对象,解决了线程安全问题
但还是存在一些问题,每次调用GetInstance()时,都需要加锁,会产生一定的开销
双检锁
static CSingleton* GetInstance()
{
if(!m_pInstance)
{
mtx.lock();
if(!m_pInstance)
{
m_pInstance = new CSingleton;
}
mtx.unlock();
}
return m_pInstance;
}
第一次判断,如果加锁前就有实例,就不需要再加锁进入了
第二次判断,如果线程进入时发现已经有实例了,直接退出不能再实例化一个对象
双检锁的操作,每次调用GetInstance(),先判断实例化对象是否存在,如果存在就不用加锁而是直接返回对象,减少了加锁的开销