1.什么是单例模式
单例模式:顾名思义,这个对象是全局的,且只有唯一的一个实例(不能创建出两份)。
用途:多个线程同时访问一个全局资源时可能会用到。例如,在C++的内存管理中,内存池只有一份,但是会有多个线程访问内存池,那么内存池的设计就可能需要单例模式。所以单例模式还是比较重要的。
2.单例模式核心设计方式
2.1饿汉模式
举例:
cpp
#include <iostream>
#include <string>
using std::cout;
using std::endl;
//配置信息类
//饿汉模式:一开始(在main函数前)就创建对象
class ConfigInfo {
public:
static ConfigInfo* GetInstance() {
return &_sInfo;
}
std::string GetIp() {
return _ip;
}
void SetIp(const std::string& ip) {
_ip = ip;
}
private:
ConfigInfo()
{}
ConfigInfo(const ConfigInfo&) = delete;
ConfigInfo& operator=(const ConfigInfo&) = delete;
private:
std::string _ip = "127.0.0.1";
int _port = 80;
//声明
static ConfigInfo _sInfo;
};
//定义
ConfigInfo ConfigInfo::_sInfo;
int main() {
cout << ConfigInfo::GetInstance() << endl;
cout << ConfigInfo::GetInstance() << endl;
ConfigInfo::GetInstance()->SetIp("192.33.3.22");
cout << ConfigInfo::GetInstance()->GetIp() << endl;
return 0;
}
饿汉模式:核心设计
1.首先把配置信息类的构造,拷贝构造,赋值重载全部设置为私有,这样就不能在类外面创建对象。
2.在私有成员中,声明一个静态的类对象,然后在类外面定义,(相当于成员函数的声明和定义分离,能够访问到私有的构造函数)这样就可以保证在全局只有唯一一个实例化的对象。(静态的成员变量不属于类,它只受类域的限制,它实际上是定义在静态区的。如果把static关键字去掉,就会"无限套娃",是错误的做法)。
3.给一个公有的静态函数:GetInstance(),这样可以通过该函数得到对象的地址或引用从而进行更多的操作。因为是静态的,所以可以通过类名访问,更方便。
饿汉模式的问题:
问题1:
如果很多单例类都是饿汉模式,有些单例对象初始化资源很多,导致程序启动慢。迟迟进不了main函数。
问题2:
如果两个单例类有初始化依赖关系,饿汉模式也无法解决。
比如A类和B类是单例,A单例要链接数据库,B单例要用A单例访问数据库。但是A和B谁先初始化不知道,这时就有问题。
2.2懒汉模式(C++11之后)
cpp
#include <iostream>
#include <string>
using std::cout;
using std::endl;
//配置信息类
//懒汉模式:在调用时创建对象
class ConfigInfo {
public:
static ConfigInfo* GetInstance() {
//局部静态单例对象
static ConfigInfo info;
return &info;
}
std::string GetIp() {
return _ip;
}
void SetIp(const std::string& ip) {
_ip = ip;
}
private:
ConfigInfo()
{
cout << "ConfigInfo" << endl;
}
ConfigInfo(const ConfigInfo&) = delete;
ConfigInfo& operator=(const ConfigInfo&) = delete;
private:
std::string _ip = "127.0.0.1";
int _port = 80;
};
int main() {
cout << ConfigInfo::GetInstance() << endl;
cout << ConfigInfo::GetInstance() << endl;
ConfigInfo::GetInstance()->SetIp("192.33.3.22");
cout << ConfigInfo::GetInstance()->GetIp() << endl;
return 0;
}
与饿汉模式相比,懒汉模式就是把静态私有成员改成了GetInstance()中的静态局部对象,这样该对象在调用时只会构造一次。这样就很好的解决了饿汉模式的问题。
但这样写也有一个小问题,就是在C++11之前无法保证GetInstance()是线程安全的,也就是说当两个线程同时调用该函数时,可能会构造两次。但在C++11之后,编译器经过特殊处理了,就没问题了。如果你的编译器不支持C++11就不能用这种写法。
2.3懒汉模式(C++11之前)
cpp
#include <iostream>
#include <string>
#include <mutex>
using std::cout;
using std::endl;
//配置信息类
//懒汉模式:在调用时创建对象
class ConfigInfo {
public:
static ConfigInfo* GetInstance() {
//多线程调用需要考虑线程安全问题
//双检查机制
if (_spInfo == nullptr) { //提高性能
std::unique_lock<std::mutex> lock(_mtx);
if (_spInfo == nullptr) { //线程安全
_spInfo = new ConfigInfo;
}
}
return _spInfo;
}
std::string GetIp() {
return _ip;
}
void SetIp(const std::string& ip) {
_ip = ip;
}
private:
ConfigInfo()
{
cout << "ConfigInfo" << endl;
}
ConfigInfo(const ConfigInfo&) = delete;
ConfigInfo& operator=(const ConfigInfo&) = delete;
private:
std::string _ip = "127.0.0.1";
int _port = 80;
static ConfigInfo* _spInfo;
static std::mutex _mtx;
};
ConfigInfo* ConfigInfo::_spInfo = nullptr;
std::mutex ConfigInfo::_mtx;
int main() {
cout << ConfigInfo::GetInstance() << endl;
cout << ConfigInfo::GetInstance() << endl;
ConfigInfo::GetInstance()->SetIp("192.33.3.22");
cout << ConfigInfo::GetInstance()->GetIp() << endl;
return 0;
}
如果你的编译器支持C++11,就可以不考虑这种写法,因为这种写法更复杂:
声明静态的对象指针和静态的锁并初始化,因为初始化一个指针的开销可以忽略不计。然后在GetInstance()函数中采用双检查机制,第一次检查是为了提高性能,避免每次调用该函数时都会上锁。第二次检查是为了保证线程安全,确保只创建一个对象。
关于内存泄漏问题:
因为new出来的对象往往要考虑释放的问题,但是单例模式有所不同。单例模式的目的就是确保全局只有唯一的一个对象,以后就算不用了没有释放也影响不大(只有一个对象,占用内存基本可以忽略,等进程结束后会自动回收)。如果真的有特殊需求,可以查阅相关资料来释放该对象(因为不建议这么做,本文就不介绍了)。