特殊类设计与设计模式

在这里插入图片描述

🌎特殊类设计与设计模式


文章目录:

特殊类设计与设计模式

    特殊类设计
      设计一个只能在堆上创建对象的类
      设计一个只能在栈上创建对象的类
      请设计一个不能被拷贝的类
      请设计一个不能被继承的类

    设计模式
      饿汉模式
      懒汉模式


🚀特殊类设计

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

  如果只能在堆上创建对象,也就意味着在创建对象的时候必须使用new来创建对象,那么我们就需要:

解决方式一

  • 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象
  • 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
class HeapOnly {
public:
	static HeapOnly* GetHeapOnlyObj(int x, int y)
	{
		return new HeapOnly(x, y);
	}

	// 调用拷贝与赋值会发生值拷贝,出现在栈上开辟的对象,故需要禁用
	HeapOnly(const HeapOnly&) = delete;
	HeapOnly& operator=(const HeapOnly&) = delete;

private:

	HeapOnly()
	{}
	
	HeapOnly(int x, int y)
		:_x(x), _y(y)
	{}

	int _x;
	int _y;
};

int main()
{
	HeapOnly *ho = HeapOnly::GetHeapOnlyObj(1, 2);// 获取对象 返回new出来的对象,必定是在堆上开辟
	return 0;
}

  除了将构造函数私有以外,我们知道,如果没有实现拷贝构造与赋值重载,类会默认生成,并且都是以值拷贝的方式对另一个变量初始化、赋值,所以为了避免这种情况,我们 需要将拷贝构造与赋值重载禁用

解决方式二

  不一定非要把拷贝与赋值重载禁用,我们也可以把析构函数屏蔽或者禁用,但是把析构函数私有化了,最好在实现一个可调用析构函数的接口,这样创建对象只能使用new来创建对象:

class HeapOnly {
public:
	HeapOnly()
	{}

	HeapOnly(int x, int y)
		:_x(x), _y(y)
	{}

	void Destroy()
	{
		delete this;
	}

private:
	~HeapOnly()
	{
		cout << "~HeapOnly()" << endl;
	}

	int _x;
	int _y;
};

int main()
{
	HeapOnly ho1;
	HeapOnly ho2 = new HeapOnly(1, 2);
	return 0;
}

在这里插入图片描述

  如果使用智能指针来管理,直接使用智能指针是无法编译通过的:

int main()
{
	shared_ptr<HeapOnly> ptr(new HeapOnly(1, 2));
	return 0;
}

在这里插入图片描述
  实际上,这是因为我们把析构函数私有化,而 shared_ptr指针底层默认的 删除器 调用的是delete,但是现在delete调用不了,所以编译不通过,所以我们 需要自定义删除器,可以使用仿函数,但是这里更推荐使用lambda表达式来做删除器:

int main()
{
	shared_ptr<HeapOnly> ptr(new HeapOnly(1, 2), 
		[](HeapOnly* ptr) { ptr->Destroy(); }
	);
	return 0;
}

✈️设计一个只能在栈上创建对象的类

  同理,与在堆上创建对象原理相似,将构造函数私有化,在实现一个构造对象的public成员函数:

// 请设计一个类,只能在栈上创建对象
class StackOnly {
public:
	static StackOnly GetStackOnlyObj(int x, int y)
	{
		return StackOnly(x, y);
	}

	StackOnly(const StackOnly&) = delete;
	StackOnly& operator=(const StackOnly&) = delete;

private:

	StackOnly()
	{}

	StackOnly(int x, int y)
		:_x(x), _y(y)
	{}

	int _x;
	int _y;
};

int main()
{
	StackOnly so = StackOnly::GetStackOnlyObj(1, 2);
	return 0;
}

在这里插入图片描述

  这里编不过的原因是因为,我们实现的获取栈上的对象是值拷贝,返回的是局部对象,出了作用域就会销毁,所以我们需要调用拷贝构造,因为我们不能将拷贝构造delete掉:

// 请设计一个类,只能在栈上创建对象
class StackOnly {
public:
	static StackOnly GetStackOnlyObj(int x, int y)
	{
		return StackOnly(x, y);
	}

	// StackOnly(const StackOnly&) = delete;
	StackOnly& operator=(const StackOnly&) = delete;

private:

	StackOnly()
	{}

	StackOnly(int x, int y)
		:_x(x), _y(y)
	{}

	int _x;
	int _y;
};

int main()
{
	// HeapOnly *ho = HeapOnly::GetHeapOnlyObj(1, 2);// 获取对象 返回new出来的对象,必定是在堆上开辟
	StackOnly so = StackOnly::GetStackOnlyObj(1, 2);
	return 0;
}

  这个时候就可以编译通过了,这里我们把拷贝构造给放开了,就有可能会导致创建的对象是在堆上开辟的:

在这里插入图片描述

  因为 new在底层调用void* operator new(size_t size)函数,只需将该函数屏蔽掉或者禁用掉即可, 同理 delete尽量也要禁用一下:

// 请设计一个类,只能在栈上创建对象
class StackOnly {
public:
	static StackOnly GetStackOnlyObj(int x, int y)
	{
		return StackOnly(x, y);
	}

	// 重载类内专属new关键字,将来在调用时会优先调用类内new,这里将其禁用就杜绝了在堆上创建对象的可能
	void* operator new(size_t n) = delete;
	void operator delete(void* ptr) = delete;
	StackOnly& operator=(const StackOnly&) = delete;

private:

	StackOnly()
	{}

	StackOnly(int x, int y)
		:_x(x), _y(y)
	{}

	int _x;
	int _y;
};

int main()
{
	StackOnly so = StackOnly::GetStackOnlyObj(1, 2);
	StackOnly* so2 = new StackOnly(so);// 在堆上开辟空间, 开辟错误
	return 0;
}

在这里插入图片描述


✈️请设计一个不能被拷贝的类

  很简单,只需要将拷贝构造和赋值重载私有化或者禁用即可:

class NoCopy {
public:
	NoCopy()
	{}

	// NoCopy(const NoCopy&) = delete;
	// NoCopy& operator=(const NoCopy&) = delete;

private:
	NoCopy(const NoCopy&)
	{}

	No5Copy& operator=(const NoCopy&)
	{}5
};

✈️请设计一个不能被继承的类

  我们知道一个类如果可以被继承,那么当子类进行初始化时,会优先调用父类构造初始化父类部分,那么我们可以按照这个思路:

解决方案一构造函数私有化

class NonInherit {
public:
	static GetNonInheritObj()
	{
		return NonInherit();
	}

private:
	NonInherit()
	{}
};

  这样就可以避免构造函数被调用,从而避免被继承。

解决方案二:使用 final 关键字

class NonInherit final
{
public:
	NonInherit()
	{}

	static GetNonInheritObj()
	{
		return NonInherit();
	}
private:
};

  该关键字是C++11提出的关键字,可以禁止继承。


🚀设计模式

  • 设计模式(Design Pattern) 是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。

  • 使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

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

  而单例模式分类又为 饿汉式懒汉式


✈️饿汉模式

  什么是饿汉模式呢?简单来说就是:不管你将来用不用,程序启动时就创建一个唯一的实例对象

  饿汉模式要求在开始main函数之前这个对象就存在有且只有一份,所以我们可以把需要的数据资源存放在类内。

  首先,一定要将构造函数给私有化,这样外部就没法随意调用构造函数创建对象,但是我们要在public区建立一个接口,返回私有对象的地址或引用,并且将此接口设置为静态,这样我们可以使用类域访问。

  那么由此接口使用的对象都会是同一个对象:

class Singleton
{	
public:
	static Singleton* GetInstance()
	{
		return &_sint;
	}

	void Print()
	{
		cout << "x: " << _x << endl;
		cout << "y: " << _y << endl;

		for (auto &e : _vstr)
		{
			cout << e << " ";
		}
		cout << endl;
	}
	
	Singlenton(const Singlenton&) = delete;// 禁用赋值与拷贝
	Singlenton& operator=(const Singlenton&) = delete;

private:
	Singleton(int x = 0, int y = 0, vector<string> vstr = { "xxxx", "yyyy" })
		:_x(x)
		, _y(y)
		, _vstr(vstr)
	{}

	int _x;
	int _y;
	vector<string> _vstr;
	// 静态成员变量存在静态区, 属于全局的,声明在类中
	static Singleton _sint;
};

// 定义和初始化
Singleton Singleton::_sint(1, 2, {"today", "I'm sad"});


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

在这里插入图片描述

  类内私有设置一个私有静态对象,这个对象实际不存在类中,但是需要类域访问,而静态成员的声明和定义分离,声明在类中,定义在类外。这样想要使用这个单例我们就需要从类域拿到GetInstance()获取单例,由单例获取成员方法工作,同时需要禁止复制与拷贝,防止使用单例再构造对象。

  看起来饿汉模式的单类模式非常实用,实际上它有以下 缺点

  • 如果单例对象的数据比较多,构造初始化成本比较高,那么会影响程序启动的速度。迟迟进入不了main函数
  • 多个单例类有初始化启动依赖关系,饿汉无法控制(假设A和B两个单例,需要A先初始化,B在初始化,但是我们无法保证哪个单例先创建)

✈️懒汉模式

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

  懒汉模式也非常简单,在饿汉模式的基础上,将单例对象更改为单例指针,这样我们就可以在GetInstance()初始化了,当第一次调用GetInstance时,就可以创建一个单例对象,而当再次调用时就为同一单例,这样就保证了单例只会在调用的时候出现。

class Singleton
{
public:
	static Singleton* GetInstance(int x = 3, int y = 4, vector<string> vstr = {"It's so", " bad!"})
	{
		// 第一次调用才产生单例
		if (_psint == nullptr)
		{
			_psint = new Singleton(x, y, vstr);
		}
		
		return _psint;
	}

	void Print()
	{
		cout << "x: " << _x << endl;
		cout << "y: " << _y << endl;

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

private:
	Singleton(int x = 0, int y = 0, vector<string> vstr = { "xxxx", "yyyy" })
		:_x(x)
		, _y(y)
		, _vstr(vstr)
	{}

	int _x;
	int _y;
	vector<string> _vstr;
	// 静态成员变量存在静态区, 属于全局的,定义在类中
	static Singleton *_psint;
};

// 初始化
Singleton* Singleton::_psint = nullptr;

int main()
{
	// hanger::Singleton::GetInstance()->Print();
	lazy::Singleton::GetInstance(5, 6)->Print();
	return 0;
}

在这里插入图片描述

  这样的单例模式并不是一个较为完整的单例模式,因为我们没有释放资源,而析构函数一定不能被显示调用,所以也需要放在类私有部分,那么我们就需要在public部分实现一个接口,让接口回调类内析构。

class Singleton
{
public:
	static Singleton* GetInstance(int x = 3, int y = 4, vector<string> vstr = {"It's so", " bad!"})
	{
		// 第一次调用才产生单例
		if (_psint == nullptr)
		{
			_psint = new Singleton(x, y, vstr);
		}
		
		return _psint;
	}

	void Print()
	{
		cout << "x: " << _x << endl;
		cout << "y: " << _y << endl;

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

	void DelInstance()
	{
		delete Singleton::_psint;
	}

private:
	~Singleton()
	{
		cout << "~Singleton" << endl;
	}
	
	Singleton(int x = 0, int y = 0, vector<string> vstr = { "xxxx", "yyyy" })
		:_x(x)
		, _y(y)
		, _vstr(vstr)
	{}

	int _x;
	int _y;
	vector<string> _vstr;
	// 静态成员变量存在静态区, 属于全局的,定义在类中
	static Singleton *_psint;
};

// 初始化
Singleton* Singleton::_psint = nullptr;

int main()
{
	// hanger::Singleton::GetInstance()->Print();
	lazy::Singleton::GetInstance(5, 6)->Print();
	return 0;
}

  但是这样我们只能手动调用了,很容易导致程序员忘记手动添加删除资源,所以我们想要实现一个可以自动释放资源的类,我们可以考虑内部类。在public区域,我们实现一个辅助删除类,类内只有自己的析构函数,而析构函数的作用是调用DelInstance(),而我们在private区域定义一个辅助类的静态对象,当main函数结束时,static生命周期也就到了,会自动调用析构函数,这样就可以调用DelInstance()函数清理懒汉的单例模式了。

class Singleton
{
public:
	static Singleton* GetInstance(int x = 3, int y = 4, vector<string> vstr = {"It's so", " bad!"})
	{
		if (_psint == nullptr)
		{
			_psint = new Singleton(x, y, vstr);
		}
		
		return _psint;
	}

	void Print()
	{
		cout << "x: " << _x << endl;
		cout << "y: " << _y << endl;

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

	static void DelInstance()
	{
		if (Singleton::_psint)
		{
			delete Singleton::_psint;
		}
	}

	class AssisDele
	{
	public:
		~AssisDele()
		{
			Singleton::DelInstance();
		}
	};

private:
	~Singleton()
	{
		cout << "~Singleton()" << endl;
	}

	Singleton(int x = 0, int y = 0, vector<string> vstr = { "xxxx", "yyyy" })
		:_x(x)
		, _y(y)
		, _vstr(vstr)
	{}

	static AssisDele _ad;
	int _x;
	int _y;
	vector<string> _vstr;
	// 静态成员变量存在静态区, 属于全局的,定义在类中
	static Singleton *_psint;
};

// 初始化
Singleton* Singleton::_psint = nullptr;
Singleton::AssisDele _ad;

int main()
{
	lazy::Singleton::GetInstance(5, 6)->Print();
	return 0;
}

在这里插入图片描述

  懒汉模式缺点

  • 懒汉模式在调用GetInstance()时需要加锁(C++线程安全篇详解),不然可能多个线程同时调用生成多个对象。
  • 实现起来比较复杂。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/897547.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【汇编语言】第一个程序(一)—— 一个源程序从写出到执行的过程

文章目录 前言1. 第一步&#xff1a;编写汇编源程序2. 第二步&#xff1a;对源程序进行编译连接3. 第三步&#xff1a;执行可执行文件中的程序结语 前言 &#x1f4cc; 汇编语言是很多相关课程&#xff08;如数据结构、操作系统、微机原理&#xff09;的重要基础。但仅仅从课程…

【GIT】.cr、.gitattributes 、 .gitignore和.git各文件夹讲解介绍

在 Git 项目中&#xff0c;.cr、.gitattributes 和 .gitignore 文件分别用于不同的配置和管理功能。下面分别解释这些文件的作用和用途&#xff1a; 1. .gitignore 文件 作用&#xff1a; .gitignore 文件用于指定哪些文件或目录应该被 Git 忽略&#xff0c;不会被追踪或提交…

大数据-185 Elasticsearch - ELK 家族 Logstash 安装配置 Input 插件-stdin stdout

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

「C/C++」C++ STL容器库 之 std::string 字符串类

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「C/C」C/C程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasoli…

vue使用jquery的ajax,页面跳转

一、引入jquery依赖 打开终端更新npm npm install -g npm 更新完后引入输入npm install jquery 加载完后 在最外层的package.json文件中加入以下代码 配置好后导入jquery 设置变量用于接收服务器传输的数据 定义ajax申请数据 服务器的Controller层传输数据 &#xff08;…

linux介绍与基本指令

前言 本次博客将会讲解linux的来源历史、linux操作系统的理解以及它的一些基本指令。 1.linux的介绍 linux的来源 linux的来源最初还是要说到unix操作系统的。 1968年&#xff0c;一些来自通用电器公司、贝尔实验室和麻省理工学院的研究人员开发了一个名叫Multics的特殊操作…

C++ 基于自主实现的红黑树封装Map和Set (下)

C 基于自主实现的红黑树封装Map和Set &#xff08;上&#xff09;-CSDN博客 本文针对上文中没有完成的迭代器接口进行一个补充。 1. 箭头访问 在map的测试中使用箭头访问测试&#xff0c;我们可以复习到: 测试刚才重载的-> , 出现了经典双箭头问题 按理来说应该是像下图一样…

uniapp-components(封装组件)

<myitem></myitem> 在其他类里面这样调用。

Python数值计算(28)——理查森外推法

1. 基础知识 理查森外推法( Richardson extrapolation)是一种提高某些数值过程精度的简单方法&#xff0c;在数值方法中广泛应用。 理查森外推法的基本思想是通过对原函数进行多次求导&#xff0c;并在每一步求导的基础上进行线性组合&#xff0c;得到一个新的函数&#xff0c…

智能时代摩托车一键启动无钥匙进入感受科技前线

向智能化与高性能迈进,技术创新与绿色转型引领摩托车行业智能化出行。 摩托车一键启动无钥匙进入功能是一种先进的车辆控制系统&#xff0c;它允许驾驶员在不使用传统机械钥匙的情况下&#xff0c;通过智能感应技术自动解锁和启动摩托车。这种系统通常包括一个智能钥匙&#x…

从零开始学习 YOLOv8:目标检测与车牌识别实例

1. 引言 什么是目标检测&#xff1f; 目标检测就像是在寻找隐藏的宝藏。想象一下&#xff0c;你在一个巨大的图画里&#xff0c;里面藏着无数的物体&#xff0c;而你的任务是迅速找到其中的几样&#xff0c;比如说&#xff0c;一只流浪的小猫和一辆红色的小轿车。目标检测就是…

HTML作业

作业 复现下面的图片 复现结果 代码 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title></head><body><form action"#"method"get"enctype"text/plain"><…

【实验六】基于前馈神经网络的二类任务

1 数据集构建 2 模型构建 2.1 线性层算子 2.2 Logistic算子 2.3 层次串行组合 3 损失函数 4 模型优化 4.1 反向传播算法 4.2 损失函数 4.3 Logistic算子 4.4 线性层 4.5 整个网络 4.6 优化器 5 完善Runner类&#xff1a;RunnerV2_1 6 模型训练 7 性能评价 8 完…

Java应用程序的测试覆盖率之设计与实现(二)-- jacoco agent

说在前面的话 要想获得测试覆盖率报告&#xff0c;第一步要做的是&#xff0c;采集覆盖率数据&#xff0c;并输入到tcp。 而本文便是介绍一种java应用程序部署下的推荐方式。 作为一种通用方案&#xff0c;首先不想对应用程序有所侵入&#xff0c;其次运维和管理方便。 正好…

高级的SQL查询技巧有哪些?

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///C爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于高级SQL查询技巧方面的相关内容&#xf…

协程必知必会-系列4-协程本地变量

文章目录 协程本地变量相关结构体实现原理代码实现代码示例思考题 协程本地变量 在上一篇文章中&#xff0c;我们介绍了如何通过协程来实现批量并发执行&#xff0c;本篇文章将向大家介绍如何在协程的基础之上&#xff0c;实现协程本地变量。 注意&#xff1a;「为了减轻大家…

Docker基础部署

一、安装Ubuntu系统 1.1 新建虚拟机 打开VMware Workstation&#xff0c;选择文件->新建虚拟机->典型&#xff08;推荐T&#xff09;->安装程序光盘映像文件->输入虚拟的名字->一直下一步即可 安装程序光盘映像文件 注意&#xff1a;选择CentOS-7-x86_64-DVD-…

Springboot 使用EasyExcel导出Excel文件

Springboot 使用EasyExcel导出Excel文件 Excel导出系列目录&#xff1a;引入依赖创建导出模板类创建图片转化器 逻辑处理controllerservice 导出效果遗留问题 Excel导出系列目录&#xff1a; 【Springboot 使用EasyExcel导出Excel文件】 【Springboot 使用POI导出Excel文件】 …

大模型带来新安全机遇

当前网络空间安全面临攻击隐蔽难发现、数据泄露风险高和违法信息审核难等挑战。大模型展现出强大的信息理解、知识抽取、意图和任务编排等能力&#xff0c;为网络空间安全瓶颈问题提供了新的解决思路和方法。与此同时&#xff0c;大模型发展也催生了恶意软件自动生成、深度伪造…

vue 项目i18n国际化,快速抽离中文,快速翻译

国际化大家都知道vue-i18n 实现的&#xff0c;但是有个问题&#xff0c;就是繁杂的抽离中文字符的过程&#xff0c;以及翻译中文字符的过程&#xff0c;关于这个有些小工具可以希望可以帮到大家 1.安装vue-i18n npm i vue-i18n8.22.22.ElementUI多语言配置 在src目录下创建…