C++单例模式

在编程的时候,有一些场景经常需要我们保证这个类只能存在一个对象,这就叫做单例模式。

什么是单例模式?

一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个
访问它的全局访问点,该实例被所有程序模块共享 。比如在某个服务器程序中,该服务器的配置
信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再
通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

单例模式的分类

单例模式的实现一般分为饿汉模式和懒汉模式。

饿汉模式

所谓饿汉模式,就是"饿"了,急需要吃,即创建出对象,就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。

懒汉模式

所谓懒汉模式,是指比较"懒",第一个人需要使用时再去实例对象。

实现单例模式

下面我们来实现一下单例模式,以便更深刻了解

饿汉模式

因为需要一个类只能存在一个对象,所以首先我们需要把构造函数私有吧,避免外面有人直接去实例化对象,其次也需要把拷贝构造封掉,因为可能会有人拿唯一的对象去拷贝构造出一个新的,那就还有赋值重载。

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;
};
相关推荐
加载中loading...16 分钟前
Linux线程安全(二)条件变量实现线程同步
linux·运维·服务器·c语言·1024程序员节
Wx120不知道取啥名20 分钟前
C语言之长整型有符号数与短整型有符号数转换
c语言·开发语言·单片机·mcu·算法·1024程序员节
biomooc1 小时前
R语言 | paletteer包:拥有2100多个调色板!
r语言·数据可视化·1024程序员节
Hello.Reader1 小时前
FFmpeg 深度教程音视频处理的终极工具
ffmpeg·1024程序员节
Y.O.U..2 小时前
STL学习-容器适配器
开发语言·c++·学习·stl·1024程序员节
就爱敲代码2 小时前
怎么理解ES6 Proxy
1024程序员节
憧憬一下2 小时前
input子系统的框架和重要数据结构详解
arm开发·嵌入式·c/c++·1024程序员节·linux驱动开发
三日看尽长安花3 小时前
【Tableau】
1024程序员节
sswithyou3 小时前
Linux的调度算法
1024程序员节
武子康3 小时前
大数据-187 Elasticsearch - ELK 家族 Logstash Filter 插件 使用详解
大数据·数据结构·elk·elasticsearch·搜索引擎·全文检索·1024程序员节