C++特殊类与单例模式

一、特殊类

类的特殊设计方式

①不能被拷贝的类

拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝, 只需让该类不能调用拷贝构造函数以及赋值运算符重载即可

在C++98中,需要将拷贝构造设置成私有,并且只声明不定义,是因为该构造函数根本不会调用,定义了也并无意义

cpp 复制代码
//不能被拷贝的类
//C++98
class CopyBan
{

private:
	CopyBan(const CopyBan&);
	CopyBan& operator=(const CopyBan&);
};

//C++11
class CopyBan
{
	CopyBan(const CopyBan&) = delete;
	CopyBan& operator=(const CopyBan&) = delete;
};

②只能在堆上创建对象的类

实现方法:

  1. 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象

  2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建

cpp 复制代码
//只能在堆上创建对象的类
class HeapOnly
{
public:
	static HeapOnly* CreateObject()
	{
		return new HeapOnly;
	}
private:
	HeapOnly() {}
	HeapOnly(const HeapOnly&);//C++98
	HeapOnly(const HeapOnly&) = delete;//C++11
};

③只能在栈上创建对象的类

将构造函数私有化,然后设计静态方法创建对象返回

禁用operator new可以把下面用new 调用拷贝构造申请对象给禁止

cpp 复制代码
//只能在栈上创建对象的类
class StackOnly
{
public:
	static StackOnly CreateObject(int x=0)
	{
		return StackOnly(x);
	}

	void* operator new(size_t size) = delete;
	void operator delete(void* p) = delete;

	StackOnly(StackOnly&& st)
		//拷贝构造被禁止之后,会导致创建StackOnly st1=StackOnly::CreateObj(1)失败,因此需要开放移动构造
		//但这样又会导致static StackOnly st2=move(st1)构造成功,因此并不能完全禁止
		:_x(st._x)
	{}
private:
	StackOnly(int x=0)
		:_x(x)
	{}

	StackOnly(const StackOnly& st) = delete;//禁止静态对象拷贝

private:
	int _x;
};

④不能被继承的类

cpp 复制代码
//不能被继承的类
//C++98
class NonInherit
{
public:
	static NonInherit GetInstance()
	{
		return NonInherit();
	}
private:
	NonInherit() {}
};

//C++11
class A final
{

};

二、单例模式

使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保障代码的可靠性

单例模式:一个类只能创建一个对象,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享

单例模式有两种实现模式:饿汉模式和懒汉模式

饿汉模式:

饿汉模式是指:不论未来是否使用,在main函数执行之前,就已经生成一个实例

如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避 免资源竞争,提高响应速度更好

饿汉式是线程安全的,在类创建的同时就已经创建好一个静态的对象供系统使用,以后不在改变。

cpp 复制代码
#include<iostream>
#include<mutex>
#include<thread>
#include<vector>
#include<string>
#include<time.h>
using namespace std;

//单例对象:只能创建一个对象的类


//饿汉模式
//在main函数程序执行之前,就已经有了一个实例
class Singleton
{
public:
	static Singleton* GetInstance()
	{
		return _ins;
	}

	void Add(const string& str)
	{
		_mtx.lock();

		_v.push_back(str);

		_mtx.unlock();
	}

	void Print()
	{
		_mtx.lock();
		
		for (auto& e : _v)
		{
			cout << e << endl;
		}
		cout << endl;
		
		_mtx.unlock();
	}
private:
	Singleton() {};//限制类外随意创建对象

	//C++98防止拷贝
	//Singleton(Singleton const&);
	//Singleton& operator=(Singleton const&);

	//C++11防拷贝
	Singleton(Singleton const&) = delete;
	Singleton& operator=(Singleton const&) = delete;
	
private:
	static Singleton* _ins;

	mutex _mtx;
	vector<string> _v;
};

Singleton* Singleton::_ins=new Singleton;//在程序入口之前,就完成对单例对象的初始化




int main()
{
	srand(time(0));

	int n = 30;
	thread t1([n]() {
		for (size_t i = 0; i < n; ++i)
		{
			Singleton::GetInstance()->Add("t1线程:" + to_string(rand()));
		}
		});

	thread t2([n]() {
		for (size_t i = 0; i < n; ++i)
		{
			Singleton::GetInstance()->Add("t2线程:" + to_string(rand()));
		}
		});

	t1.join();
	t2.join();

	Singleton::GetInstance()->Print();
	return 0;
}

懒汉模式:

懒汉模式顾名思义:只有在第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控制

如果单例对象构造十分耗时或者占用很多资源,比如加载插件、初始化网络连接、读取文件等等,而有可能该对象程序运行时不会用到。但也要在程序一开始就进行初始化, 就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好 、

懒汉模式的实现相比于饿汉模式更加复杂。既然是手动启用实例,就需要考虑手动释放实例问题,手动释放实例之后不能影响自动释放实例,并且也不能重复释放

自动释放问题:定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象

懒汉模式中需要考虑线程安全问题:因此设计双检查加锁,第一次检查用于提高效率,避免每次访问单例都加减锁;第二次检查用于保证线程安全并确保单例只new一次

私有类内有两个锁,一个全局锁一个局部锁,全局锁存在的目的是使得单例获取与释放时都全局同一,保障全局唯一单例

cpp 复制代码
#include<iostream>
#include<mutex>
#include<thread>
#include<vector>
#include<string>
#include<time.h>
using namespace std;

//懒汉模式
//第一次调用时才会创建单例
class Singleton
{
public:

	~Singleton()
	{
		//程序可能有持久化需求,要求在程序结束时,将数据写到文件中
	}
	static Singleton* GetInstance()
	{
		//双检查加锁
		if (_ins == nullptr)//用于提高效率 避免每次访问单例都加减锁
		{
			_imtx.lock();

			if (_ins == nullptr)//保证线程安全和只new一次
			{
				_ins = new Singleton;
			}

			_imtx.unlock();
		}
		return _ins;
	}


	void Add(const string& str)
	{
		_vmtx.lock();

		_v.push_back(str);

		_vmtx.unlock();
	}

	void Print()
	{
		_vmtx.lock();

		for (auto& e : _v)
		{
			cout << e << endl;
		}
		cout << endl;

		_vmtx.unlock();
	}




	//显示手动释放单例
	static void DelInstance()//一般全局都要使用单例对象,所以一般情况下不需要显示释放
	{
		_imtx.lock();
		if (_ins)
		{
			delete _ins;
			_ins = nullptr;//显示释放后置空,这样自动释放重复也不影响
		}
		_imtx.unlock();
	}

	//保证单例对象的回收(自动回收)
	class GC
	{
	public:
		~GC()
		{
			DelInstance();
		}
	};

	static GC _gc;

private:
	Singleton() {}//限制类外随意创建对象
	//没有处理拷贝构造,是由于锁的存在,默认生成的拷贝构造
	//实际上仍然需要防拷贝
	Singleton(const Singleton& s) = delete;
	Singleton& operator=(const Singleton& s) = delete;

private:
	mutex _vmtx;
	vector<string> _v;

	static Singleton* _ins;//静态成员类外实现
	static mutex _imtx;
};

Singleton* Singleton::_ins = nullptr;
mutex Singleton::_imtx;

Singleton::GC Singleton::_gc;


int main()
{
	srand(time(0));

	int n = 30;
	thread t1([n]() {
		for (size_t i = 0; i < n; ++i)
		{
			Singleton::GetInstance()->Add("t1线程:" + to_string(rand()));
		}
		});

	thread t2([n]() {
		for (size_t i = 0; i < n; ++i)
		{
			Singleton::GetInstance()->Add("t2线程:" + to_string(rand()));
		}
		});

	t1.join();
	t2.join();

	Singleton::GetInstance()->Print();

	return 0;
}
相关推荐
hakesashou1 分钟前
python怎么看矩阵维数
开发语言·python
daopuyun10 分钟前
GB/T34944-2017 《Java语言源代码漏洞测试规范》解读——安全功能
java·开发语言·安全
A懿轩A17 分钟前
C/C++ 数据结构与算法【树和二叉树】 树和二叉树,二叉树先中后序遍历详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·二叉树·
qh0526wy21 分钟前
pyqt5冻结+分页表
开发语言·python·qt
hjxxlsx28 分钟前
探索 C++ 自定义函数的深度与广度
开发语言·c++
罗政1 小时前
PDF书籍《手写调用链监控APM系统-Java版》第12章 结束
java·开发语言·pdf
匹马夕阳1 小时前
详细对比JS中XMLHttpRequest和fetch的使用
开发语言·javascript·ecmascript
月巴月巴白勺合鸟月半1 小时前
一个特别的串口通讯
开发语言·串口通讯
乄北城以北乀1 小时前
第1章 R语言中的并行处理入门
开发语言·分布式·r语言
全栈老实人_1 小时前
农家乐系统|Java|SSM|VUE| 前后端分离
java·开发语言·tomcat·maven