在编程的时候,有一些场景经常需要我们保证这个类只能存在一个对象,这就叫做单例模式。
什么是单例模式?
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个
访问它的全局访问点,该实例被所有程序模块共享 。比如在某个服务器程序中,该服务器的配置
信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再
通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
单例模式的分类
单例模式的实现一般分为饿汉模式和懒汉模式。
饿汉模式
所谓饿汉模式,就是"饿"了,急需要吃,即创建出对象,就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。
懒汉模式
所谓懒汉模式,是指比较"懒",第一个人需要使用时再去实例对象。
实现单例模式
下面我们来实现一下单例模式,以便更深刻了解
饿汉模式
因为需要一个类只能存在一个对象,所以首先我们需要把构造函数私有吧,避免外面有人直接去实例化对象,其次也需要把拷贝构造封掉,因为可能会有人拿唯一的对象去拷贝构造出一个新的,那就还有赋值重载。
cpp
#include<iostream>
#include<vector>
using namespace std;
class hungersingle
{
private:
hungersingle(int x = 10, int y = 50, vector<string> vv = { "你好","我不好" })
:_x(x)
, _y(y)
, vs(vv)
{
cout << "hunfersingle构造" << endl;
}
hungersingle(const hungersingle&) = delete;
hungersingle operator=(const hungersingle&) = delete;
int _x;
int _y;
vector<string> vs;
};
而我们需要保证在程序运行前只能存在一个对象,那这个对象必须是"全局"的,所以我们可以用一个静态成员变量,该类只有一个静态成员。
cpp
static hungersingle hs;
我们提供一个接口去获取该静态成员即可
cpp
static hungersingle* getobject()
{
return &hs;
}
这样一个饿汉单例模式就好啦,饿汉是比较简单的。再给他提供一些接口测试一下
cpp
class hungersingle
{
public:
static hungersingle* getobject()
{
return &hs;
}
void Print()
{
cout << _x << endl;
cout << _y << endl;
for (auto& e : vs)
cout << e << " ";
cout << endl;
}
void addstr(const string& str)
{
vs.emplace_back(str);
}
void changeINT(int m, int n)
{
_x = m;
_y = n;
}
hungersingle(const hungersingle&) = delete;
hungersingle operator=(const hungersingle&) = delete;
private:
hungersingle(int x = 10, int y = 50, vector<string> vv = { "你好","我不好" })
:_x(x)
, _y(y)
, vs(vv)
{
cout << "hunfersingle构造" << endl;
}
int _x;
int _y;
vector<string> vs;
static hungersingle hs;//类内声明
};
hungersingle hungersingle::hs;//类外面定义
饿汉单例模式虽然简单,但是也是会有一些缺点的,因为它需要在程序启动时main函数之前就把对象实例化出来,我们这个类比较简单,如果有的类实例化对象时需要申请大量资源,那么程序运行后半天实例化不出对象,可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定。
懒汉模式
如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取
文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,
就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式更好。
懒汉模式的核心思想是有一个静态成员指针,第一个用它的人去实例化。
首先它也需要构造函数私有,封掉拷贝构造和复制重载
cpp
#include<iostream>
#include<vector>
#include<mutex>
#include<string>
using namespace std;
class lazysingle
{
lazysingle(const lazysingle&) = delete;
lazysingle operator=(const lazysingle&) = delete;
private:
lazysingle(int x = 10, int y = 50, vector<string> vv = { "你好","我不好" })
:_x(x)
, _y(y)
, vs(vv)
{
}
~lazysingle()
{
cout << "~lazysingle()" << endl;
}
int _x;
int _y;
vector<string> vs;
static lazysingle* ls_ptr;
};
lazysingle* lazysingle::ls_ptr = nullptr;
刚才说了懒汉的思想是第一个人去实例化对象,即
cpp
static lazysingle* getobject()
{
if (ls_ptr == nullptr)
{
ls_ptr = new lazysingle;
}
return ls_ptr;
}
但是我们需要注意的是,如果是在多线程场景下,这是有线程安全问题的,因为如果两个执行流几乎同时执行if (ls_ptr == nullptr),第一个执行流刚进到if内部,因为时间片到了被切出去,第二个执行流又去判断if (ls_ptr == nullptr)也进去就会出问题。所以这里需要加锁保证安全。
cpp
static lazysingle* getobject()
{
if (ls_ptr == nullptr)
{
unique_lock<mutex> lock(_mx);//用锁防止同时有人来申请
if (ls_ptr == nullptr)
ls_ptr = new lazysingle;
}
return ls_ptr;
}
因为懒汉这个这个对象是new出来的,所以不要忘记delete,为了防止内存泄漏,可以托管给其它对象处理,即
cpp
static void Destroy()
{
if (ls_ptr)
{
delete ls_ptr;
ls_ptr = nullptr;
}
}
class Gc//定义这个类让他的析构自动调用lazysingle的destroy,防止忘记调用
{
public:
~Gc()
{
lazysingle::Destroy();
}
};
static Gc gc;
先写一个destroy函数调用delete,再写一个内部类,创建一个静态成员变量,当程序结束时该变量调用析构自动去调用lazysingle::Destroy();完整代码如下:
cpp
class lazysingle
{
public:
static lazysingle* getobject()
{
if (ls_ptr == nullptr)
{
unique_lock<mutex> lock(_mx);//用锁防止同时有人来申请
if (ls_ptr == nullptr)
ls_ptr = new lazysingle;
}
return ls_ptr;
}
void Print()
{
cout << _x << endl;
cout << _y << endl;
for (auto& e : vs)
cout << e << " ";
cout << endl;
}
static void Destroy()
{
if (ls_ptr)
{
delete ls_ptr;
ls_ptr = nullptr;
}
}
void addstr(const string& str)
{
vs.emplace_back(str);
}
void changeINT(int m, int n)
{
_x = m;
_y = n;
}
lazysingle(const lazysingle&) = delete;
lazysingle operator=(const lazysingle&) = delete;
private:
lazysingle(int x = 10, int y = 50, vector<string> vv = { "你好","我不好" })
:_x(x)
, _y(y)
, vs(vv)
{
}
~lazysingle()
{
cout << "~lazysingle()" << endl;
}
int _x;
int _y;
vector<string> vs;
static lazysingle* ls_ptr;
static mutex _mx;
class Gc//定义这个类让他的析构自动调用lazysingle的destroy,防止忘记调用
{
public:
~Gc()
{
lazysingle::Destroy();
}
};
static Gc gc;
};
mutex lazysingle::_mx;
lazysingle* lazysingle::ls_ptr=nullptr;
lazysingle::Gc lazysingle::gc;
main函数执行下面语句
cpp
int main()
{
lazysingle::getobject()->Print();
return 0;
}
C++ 11 对static局部变量的初始化语义进行了改进,使得在多线程环境下,static局部变量的初始化是线程安全的,即原子的。
所以C++11之后可以对懒汉模式不加锁实例化静态局部对象
cpp
class lazysingle
{
public:
static lazysingle* getobject()
{
static lazysingle laz;//定义局部静态对象,只有第一次调用这个函数的时候才会创建这个对象//C++11后这是一步原子的操作
return &laz;
}
void Print()
{
cout << _x <<endl;
cout << _y << endl;
for (auto& e : _vs)
cout << e << " ";
cout << endl;
}
void addstr(const string& ret)
{
_vs.push_back(ret);
}
void addINT(int m,int n)
{
_x = m;
_y = n;
}
lazysingle(const lazysingle&) = delete;
lazysingle operator=(const lazysingle&) = delete;
private:
lazysingle(int x=100,int y=200,vector<string> vs={"高考","中考","殿试"})
:_x(x)
,_y(y)
,_vs(vs)
{
cout << "lazysingle构造" << endl;
}
int _x;
int _y;
vector<string> _vs;
};