C++:特殊类和单例模式

文章目录

不能被拷贝的类

设计一个不能被拷贝的类,通常来说方法就是把拷贝构造和赋值重载都设置为私有,这样就不能进行拷贝了

cpp 复制代码
class A
{
public:
	A()
	{}
private:
	A(const A& a);
	A& operator=(const A& a);
};

对于C++11来说,新增了delete的用法,可以直接删除这个函数,也能做到相同的效果

cpp 复制代码
class A
{
public:
	A()
	{}
	A(const A& a) = delete;
	A& operator=(const A& a) = delete;
private:
};

设计一个只能在堆上创建对象的类

这是一个比较奇怪的要求,但是也有对应实现的方法:

  1. 把类的构造函数设置为私有
  2. 提供一个静态的成员函数,通过函数调用来进行堆上对象的创建
cpp 复制代码
// 将构造函数都设置为私有
class A
{
private:
	A();
	A(const A&);
	A& operator=(const A&);
};

int main()
{
	A* pa = new A;
	A a;
}

这样就可以保证无法创建出对象,但是同样的也无法直接new出一个对象,因为new操作符底层是调用了operator new和构造函数

解决的方法是,设置一个静态的成员函数,用来完成堆上对象的创建

cpp 复制代码
class A
{
public:
	static A* create()
	{
		A* pa = new A;
		return pa;
	}
private:
	A();
	A(const A&);
	A& operator=(const A&);
};

int main()
{
	A* pa = A::create();
	return 0;
}

到这里正好进行回顾一下static修饰成员函数表示的意义:

  1. static修饰成员函数,代表的是这个成员函数在类作用域内是全局函数,但是不能调用非静态成员变量和成员函数
  2. static修饰成员函数,该成员函数没有this指针

所以基于这样的原因,就可以设计出上面的特殊类,把构造函数都私有化,但是保留一个静态成员函数,这样就可以通过类的作用域来调用new一个对象出来,但不能在栈上开辟空间

设计一个类,只能在栈上创建对象

这个实现的逻辑其实和前面基本相同,但是唯一的区别是要防止通过new操作符,因此可以这样设计

cpp 复制代码
class A
{
public:
	static A create()
	{
		return A();
	}
	
	void* operator new(size_t size) = delete;
	void operator delete(void* p) = delete;
private:
	A()
	{}
};

int main()
{
	A a = A::create();
	A* pa = new A(a);
	return 0;
}

单例模式:设计一个只能创建一个对象的类

什么是单例模式?

单例模式指的是,一个类只能创建一个对象,保证系统中只存在这样一个实例,提供访问它的全局访问点,这个实例被所有的程序模块所共享

使用场景?

简单来说,单例模式的使用场景之一就是对于一些特殊的情况,例如对于内存池来说,如果使用的不是单例模式,可能会创建出很多的内存池,那么在进行空间的申请就会有很奇怪的错误出现,不方便管理内存,因此有了单例模式,不管在什么场景下都只能有一个实例,所有的操作都必须在这个实例下进行,就完成了单例模式设计的初衷

如何设计单例模式?

单例模式的设计通常有两种,一种是饿汉模式,一种是懒汉模式,那么下面就基于这两种模式分别进行设计

饿汉模式

不管你将来用不用,程序启动时就创建一个唯一的实例对象'''

如何保证是只有一个?保证的原理基础是用static修饰成员变量,但这个成员变量就是一种类的实例

static修饰成员变量?

static修饰成员变量,表示一种声明,初始化要在类体外进行初始化,简单来说就是只是在类的内部声明了有这样的一个实例,但是并不占用类的内部内容,并且更便携的是,它还属于这个类,就意味着它可以调用类的内部成员函数完成一些内容,并且整个作用域内只有一份

cpp 复制代码
// 单例
// 饿汉模式:提前(main函数启动时)创建好实例对象
// 优点:实现简单
// 缺点:1、可能会导致进程启动慢、2、如果两个单例有启动先后顺序,那么饿汉无法控制
class A
{
public:
	static A* GetInstance()
	{
		return &_inst;
	}

	void Add(const string& key, const string& value)
	{
		_dict[key] = value;
	}

	void Print()
	{
		for (auto& kv : _dict)
		{
			cout << kv.first << ":" << kv.second << endl;
		}
		cout << endl;
	}
private:
	A()
	{}

	A(const A& aa) = delete;
	A& operator=(const A& aa) = delete;

	map<string, string> _dict;
	int _n = 0;

	static A _inst;
};

A A::_inst;

从上面的示例代码中其实可以看出这样的原理,将这个成员变量用static在类内修饰,并且把构造函数和拷贝构造等等的函数都放到私有的部分,这样就可以使得在整个类域中只有一个实例,在外部通过构造函数是无法创建出第二个实例,并且还能调用类内的成员函数,因为它本身其实是属于类的一部分

懒汉模式

懒汉模式在饿汉模式的基础上新增了一个指针的实例化过程,就可以保证在需要的时候进行初始化

cpp 复制代码
// 懒汉模式:第一次用的时候再创建(现吃现做)
// todo:线程安全问题
// new的懒汉对象一般不需要释放,进程正常结束会释放资源
// 如果需要做一些动作,比如持久化,那么可以利用gc类static对象搞定
class B
{
public:
	static B* GetInstance()
	{
		if (_inst == nullptr)
		{
			_inst = new B;
		}

		return _inst;
	}

	void Add(const string& key, const string& value)
	{
		_dict[key] = value;
	}

	void Print()
	{
		for (auto& kv : _dict)
		{
			cout << kv.first << ":" << kv.second << endl;
		}
		cout << endl;
	}

	static void DelInstance()
	{
		if (_inst)
		{
			delete _inst;
			_inst = nullptr;
		}
	}

private:
	B()
	{}

	~B()
	{
		// 持久化:要求把数据写到文件
		cout << "数据写到文件" << endl;
	}

	B(const B& aa) = delete;
	B& operator=(const B& aa) = delete;

	map<string, string> _dict;
	int _n = 0;

	static B* _inst;

	class gc
	{
	public:
		~gc()
		{
			DelInstance();
		}
	};

	static gc _gc;
};

B* B::_inst = nullptr;
B::gc B::_gc;
相关推荐
待磨的钝刨5 分钟前
【格式化查看JSON文件】coco的json文件内容都在一行如何按照json格式查看
开发语言·javascript·json
捕鲸叉4 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer4 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq4 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
前端青山5 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
青花瓷5 小时前
C++__XCode工程中Debug版本库向Release版本库的切换
c++·xcode
从兄6 小时前
vue 使用docx-preview 预览替换文档内的特定变量
javascript·vue.js·ecmascript
幺零九零零6 小时前
【C++】socket套接字编程
linux·服务器·网络·c++
捕鲸叉7 小时前
MVC(Model-View-Controller)模式概述
开发语言·c++·设计模式
清灵xmf7 小时前
在 Vue 中实现与优化轮询技术
前端·javascript·vue·轮询