C++:特殊类的设计(无线程)

news2024/11/19 6:42:57

目录

一、设计一个不能拷贝类

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

方法一:析构函数私有化

方法二:构造函数私有化

 三、设计一个只能在栈上创建对象的类

四、设计一个类不能被继承 

五、设计一个只能创建一个对象的类(单例模式)

1.设计模式

2.单例模式 

饿汉模式 

懒汉模式

一、设计一个不能拷贝类

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

  • C++98

    将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。

class CopyBan
{
    // ...
private:
    CopyBan(const CopyBan&);
    CopyBan& operator=(const CopyBan&);
    //...
};

原因:

1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,还是能拷贝。

2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。

  • C++11

     C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。

class CopyBan
{
    // ...
    CopyBan(const CopyBan&)=delete;
    CopyBan& operator=(const CopyBan&)=delete;
    //...
};

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

方法一:析构函数私有化

 那么我们怎么创建对象呢?其实这里不能直接创建对象,只能用指针间接创建一个对象,如下所示。

 可以直接用delete吗?

不行,因为delete释放对象的资源会调用对象的析构函数,而析构函数不可访问。这该怎么办呢?

既然析构函数被设置为私有,只能够在类内调用,那我们设置一个public函数调用析构函数可以吗?如下所示:

class HeapOnly
{
public:
	void Destroy()
	{
		this->~HeapOnly();
	}
private:
	~HeapOnly()
	{
		cout << "~HeapOnly" << endl;
	}
};

//方法一:析构函数私有化
//因为没有析构函数,就只能用new申请资源赋值给类类型的指针。
int main()
{
	//HeapOnly hp1;
	HeapOnly* hp2 = new HeapOnly;
	//delete hp2;
	hp2->Destroy();
	return 0;
}

 运行一下:

调用了析构函数没有问题。

我们还可以用delete调用析构函数,

 但这种写法有些繁琐,我们可以将Destroy设置成静态的类成员函数。

 这样调用就简单一点。

注意:类的静态成员函数不能使用非静态成员变量,因为使用非静态成员变量要this,而静态成员函数中没有this。上面的静态成员函数调用的变量是ptr,ptr不是成员变量,更不是非静态成员变量,不需要this调用。delete ptr,是delete调用ptr指向空间的析构函数。

方法二:构造函数私有化

因为构造函数私有了,类外部不能直接访问,所以不能直接实例化,也不能通过new赋值给类类型指针了。

 同析构函数一样,我们也可以用间接调用构造函数的方式,创造对象指针,如下

 但是这里报错 :CreateObj显示未定义,这是为什么呢?因为CreateObj是成员函数,调用成员函数要先创建对象或对象的指针,而我们又要用CreateObj创建对象。

怎么解决这个问题呢?我们可以使用静态成员函数,

这样就行解决了。注意:这里的new调用构造函数,构造函数不需要this调用。 

但这种写法有个致命缺陷,就是可以通过拷贝构造创建栈上的变量。如下所示:

 由于是浅拷贝,所以hp4和hp3指向同一块空间。我添加一个私有成员变量check_i来核验一下,

 所以我们要把拷贝构造禁掉,

 三、设计一个只能在栈上创建对象的类

因为只能在栈上创建对象,所以要限制对象的创建方式,我们先将构造函数设置为私有,这样就只能通过类内部的某个函数调用构造函数,在这个函数中,我们通过在栈上创建变量,然后返回该变量, 同时要将这个函数设置为静态的,因为非静态成员要用隐式的this调用。

//设计一个只能在栈中创建对象的类
class StackOnly
{
public:
	static StackOnly CreateObj()
	{
		StackOnly obj;
		return obj;
	}
private:
	StackOnly()
	{
		cout << "StackOnly()" << endl;
	}
	int check_i = 666;
};
int main()
{
	StackOnly obj = StackOnly::CreateObj();
	return 0;
}

运行, 

这样只限制了构造函数,但可以通过new拷贝构造的对象申请堆上的空间,如下

 那可以把拷贝构造delete吗?

 会报错,因为传值返回会调用拷贝构造。

我们知道new分为两个步骤,一个是调用operator new ,一个是调用构造函数。

我们可以从operator new出手。对于每一个类,如果我们不显示实现类专属的operator new,它就会调用全局的operator new。如果我们实现类专属的operator new,如下

class StackOnly
{
public:
	static StackOnly CreateObj()
	{
		StackOnly obj;
		return obj;
	}
	//StackOnly(const StackOnly& obj) = delete;
	void* operator new(size_t size)
	{
		cout << "void* operator new(size_t size)" << endl;
		return malloc(size);
	}
private:
	StackOnly()
	{
		cout << "StackOnly()" << endl;
	}
	int check_i = 666;
};
int main()
{
	StackOnly obj = StackOnly::CreateObj();
	StackOnly* ptr = new StackOnly(obj);
	return 0;
}

编译器就会优先调用显示实现的operator new,我们执行一下, 

确实如此。 

所以我们可以把operator new禁掉,这样new就会报错。

四、设计一个类不能被继承 

C++98:

C++11:

 

五、设计一个只能创建一个对象的类(单例模式)

1.设计模式

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。

使用设计模式的目的:增强代码的可重用性、可理解性、可靠性,促使代码编写工程化。

2.单例模式 

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

比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。 

单例模式有两种实现模式:

  • 饿汉模式 

 程序启动时就创建一个唯一的实例对象。 (main函数启动前)

但凡是对对象有限制的,比如只能在堆上、栈上创建,第一步,就是将构造函数私有化。

如果不私有化构造函数(因为要创建对象,所以不可能delete构造函数),那么就可以通过构造函数构造多个对象。如下,我们先将构造函数私有化:

class A
{
public:
	
private:
	A()
	{}
	map<string, string> _dict;
	int _n = 0;
};
int main()
{

}

现在我们要创建一个在main()函数调用前就存在的对象,还只能创建一个。我们把问题分解为两个小问题“main函数前创造”和“只能创造一个”,我们先思考前一个问题,什么变量在main()调用前就创造好了你?好像只能是全局变量。

我们试试创建A类型的全局变量_inst。

发现报错,我们没办法直接在类外面定义一个全局变量,那我们可以在类内声明一个变量再在类外定义吗?这不就是类的静态成员变量吗?

类的静态成员只能在类内声明类外定义,我们试试,

class A
{
public:

private:
	A()
	{}
	map<string, string> _dict;
	int _n = 0;
	static A _inst;
};
A A::_inst;
int main()
{
	return 0;
}

我们运行一下程序,发现没有问题。

现在我们只要通过一个类的静态成员函数拿出_inst就行。(注意,一定要是静态的,不然访问非静态的成员函数需要对象或对象的指向,就需要构造对象)

class A
{
public:
	static A* GetInstance()
	{
		return &_inst;
	}
private:
	A()
	{}
	map<string, string> _dict;
	int _n = 0;
	static A _inst;
};
A A::_inst;

可以选择返回指针,也可以返回引用。

因为我们私有了构造函数,同时只创建一个类的静态成员变量,所以不修改当前代码,该类通过构造函数只有这一个对象(后面还要禁拷贝构造)。(同样的,如果定义了两个静态成员变量,该类就可以有两个对象)

那么我们怎么给这个对象添加数据呢?

这本质就是访问其他成员变量(非静态的成员变量),由于A中的成员变量都是私有的,所以我们只要间接的通过成员函数访问私有成员变量即可。

class A
{
public:
	static A* GetInstance()
	{
		return &_inst;
	}
	void Add(const string& key,const string& value)
	{
		_dict[key] = value;
	}

private:
	A()
	{}
	map<string, string> _dict;
	int _n = 0;
	static A _inst;
};
A A::_inst;

我们还可以写一个“打印函数”,

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()
	{}
	map<string, string> _dict;
	int _n = 0;
	static A _inst;
};
A A::_inst;

我们开始测试一下这个类。

int main()
{
	A::GetInstance()->Add("sort", "排序");
	A::GetInstance()->Add("left", "左边");
	A::GetInstance()->Add("right", "右边");
	A::GetInstance()->Print();
	return 0;
}

输出:

 我们还需要禁掉拷贝构造,不然可以通过拷贝构造创建对象,如下所示,

这样就创造了两个不同的对象。 所以我们要把拷贝构造禁掉,如果不需要自己给自己赋值,赋值重载也可以禁掉。

饿汉模式的优点:实现简单。

饿汉模式的缺点:1.可能会导致进程启动慢;

                            2.  如果有两个单例,A类单例和B类单例,分别在不同的文件中,且要区分启动的先后顺序,饿汉模式无法控制启动的先后顺序。

懒汉模式

既然懒汉模式是需要时才用,那我们可以将对象的定义到函数中,调用该函数才会真正创建对象。

为了保证只创建一个对象,我们可以用指针的接受new的对象,这样在函数可以根据指针是否为空,判断是否要创建对象,代码如下,

//懒汉模式:第一次使用时再创建(现吃现用)
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;
	}
private:
	B()
	{}
	B(const B& a) = delete;
	B& operator=(const B& a) = delete;
	map<string, string> _dict;
	int _n = 0;
	static B* _inst;
};
B* B::_inst = nullptr;
int main()
{
	B::GetInstance()->Add("sort", "排序");
	B::GetInstance()->Add("left", "左边");
	B::GetInstance()->Add("right", "右边");
	B::GetInstance()->Print();
	return 0;
}

执行,

 既然我们是new出一个对象,该资源怎么释放呢?其实new的懒汉对象一般不需要释放,进程正常结束会释放资源。(这里是指系统在进程结束后释放资源)

那么如果进程不结束,或者要提前释放资源,或者要在main()函数结束后释放资源呢?

我们可以定义一个释放资源的函数,外部可以直接调用释放的,

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

 这样我们可以随时调用这个函数释放资源。

 同时我们可以定义一个内部类,以及声明一个该类的静态成员变量,然后在类外定义这个变量。内部类的析构函数封装DelInstance(),这样在main()函数运行结束后,编译器会调用内部类静态全局变量的析构函数,也可以达到清理外部类申请资源的作用。

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;
			cout << "delete" << endl;
		}
	}
private:
	B()
	{}
	B(const B& a) = delete;
	B& operator=(const B& a) = 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;
int main()
{
	B::GetInstance()->Add("sort", "排序");
	B::GetInstance()->Add("left", "左边");
	B::GetInstance()->Add("right", "右边");
	B::GetInstance()->Print();
	return 0;
}

运行,

我们没有显示调用 DelInstance(),但还是释放资源了。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

加盖骑缝章软件、可以给PDF软件加盖自己的骑缝章

加盖骑缝章的软件多种多样&#xff0c;尤其是针对PDF文件&#xff0c;有多种软件可以实现给PDF文件加盖自己的骑缝章。以下是一些常用的软件及其特点&#xff1a; 1. Adobe Acrobat Pro DC 特点&#xff1a; 多功能PDF编辑&#xff1a;Adobe Acrobat Pro DC是一款功能强大的…

开发必备基础知识【Linux环境变量文件合集】

开发必备基础知识【Linux环境变量文件合集】 在Linux系统中&#xff0c;环境配置文件用于定制用户的Shell环境&#xff0c;包括定义环境变量、设置命令别名、定义启动脚本等。不同的Shell&#xff08;如bash、zsh&#xff09;有着各自对应的配置文件。 .bashrc&#xff1a;每新…

【推荐图书】深入浅出Spring Boot 3.x

推荐原因 这部SpringBoot3.x经典之作&#xff0c;时隔六年迎来重磅升级&#xff01; 适合java开发相关读者 购买链接 商品链接&#xff1a;https://item.jd.com/14600442.html 介绍 书名&#xff1a;深入浅出Spring Boot 3.x ISBN&#xff1a;978-7-115-63282-1/ 作者&…

C语言之Const关键字与指针

目录 1 前言2 变量与指针的储存方式3 const int *var;int *const var&#xff1b;const int *const var&#xff1b;理解与区分4 总结 1 前言 实际开发过程中经常遇到const关键字作用于指针的情况&#xff0c;例如&#xff1a;const int *var;int *const var&#xff1b;const…

红薯小眼睛接口分析与Python脚本实现

文章目录 1. 写在前面2. 接口分析3. 算法脚本实现 【&#x1f3e0;作者主页】&#xff1a;吴秋霖 【&#x1f4bc;作者介绍】&#xff1a;擅长爬虫与JS加密逆向分析&#xff01;Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Py…

比 PIP 快 100 倍的安装工具

uv 是一个由 Rust 开发的 pip 工具&#xff0c;比 pip 快 100 倍&#xff0c;难以置信&#xff0c;不过真的是快太多了。 安装 在 Mac 上直接通过 brew install uv 安装即可。 venv 创建运行环境&#xff0c;默认当前目录下 .venv uv venv 依赖安装 uv pip install -r re…

Java | Leetcode Java题解之第217题存在重复元素

题目&#xff1a; 题解&#xff1a; class Solution {public boolean containsDuplicate(int[] nums) {Set<Integer> set new HashSet<Integer>();for (int x : nums) {if (!set.add(x)) {return true;}}return false;} }

谷粒商城学习笔记-14-项目结构创建提交到码云

一&#xff0c;码云上创建工程仓库 1&#xff0c;,点击右上角加号&#xff0c;选择新建仓库 2&#xff0c;填充必要信息 ①仓库名称&#xff0c;可以理解为工程名称。 ②仓库介绍&#xff0c;添加关于仓库的说明。 ③仓库权限设置&#xff0c;如果是公司项目&#xff0c;一般…

InnoDB内部结构

在mysql数据库中&#xff0c;InnoDB存储引擎是最为常用和强大的存储引擎之一。了解InnoDB的内存结构对于优化数据库的性能&#xff0c;提高系统的稳定性和扩展性至关重要。本文将深入探讨InnoDB的内存结构。 1.Buffer Pool Buffer Pool: 缓冲池&#xff0c;其作用是用来缓存表…

为什么https比http更安全

读完本文&#xff0c;希望你能明白&#xff1a; HTTP通信存在什么问题HTTPS如何改进HTTP存在那些问题HTTPS工作原理是什么 一、什么是HTTPS HTTPS是在HTTP上建立SSL加密层&#xff0c;并对传输数据进行加密&#xff0c;是HTTP协议的安全版。现在它被广泛用于万维网上安全敏感…

奇舞周刊第532期:奇舞团生日快乐~

时光荏苒&#xff0c;岁月如歌&#xff0c;转眼间&#xff0c;奇舞团13岁啦&#x1f382;&#x1f382;&#x1f382;《奇舞周刊》也陪伴大家来到了第532期。&#x1f44f;&#x1f44f; 致敬每一位读者和创作者&#xff0c;是你们的热情、陪伴和鼓励&#xff0c;让我们不断前进…

SpringBoot | 大新闻项目源码打包

对于一个完成好的后端项目&#xff0c;如何进行打包发送给其他人&#xff0c;在电脑上进行查看 1.在pom.xml添加&#xff1a; <build><plugins> <!-- 打包插件--><plugin><groupId>org.springframework.boot</groupId><art…

【HTML入门】第二课 - head标签下的常见表情们

目录 1 本节概要 2 head下的常见标签 2.1 网页编码设置 2.2 网页的标题 2.3 样式标签 3 head标签的内容不会显示到网页上 4 查看网页源代码 1 本节概要 上一节&#xff0c;我们说了HTML网页最基本的框架标签&#xff0c;说到标签分为head头部和body身体部分。这一小节呢…

文言文编程语言|老外来了也得先学论语

最近看到一个有意思的开源项目 wenyan&#xff0c;主要功能就是使用文言文来编写代码。 按项目说明 “Wenyan” 是一种遵循中国古典文学的语法和语调的编程语言。 此外&#xff0c;文言的字符集仅包含繁体汉字和「」引号&#xff0c;确保古代中国人能够阅读。 该编程语言的文…

C++:类的成员属性,公有,私有,保护

在C中级别&#xff0c;类的成员属性分为三种访问控制&#xff1a;公有&#xff08;public&#xff09;、私有&#xff08;private&#xff09;和保护&#xff08;protected&#xff09;。它们用于控制类内部数据对类外部的可见性和访问权限。设置访问属性对于封装和信息隐藏至关…

Truffle学习笔记

Truffle学习笔记 安装truffle, 注意: 虽然目前truffle最新版是 5.0.0, 但是经过我实践之后, 返现和v4有很多不同(比如: web3.eth.accounts; 都获取不到账户), 还是那句话: “nodejs模块的版本问题会搞死人的 !” 目前4.1.15之前的版本都不能用了, 只能安装v4.1.15 npm instal…

Beats:使用 Filebeat 从 Python 应用程序中提取日志

本指南演示了如何从 Python 应用程序中提取日志并将其安全地传送到 Elasticsearch Service 部署中。你将设置 Filebeat 来监控具有标准 Elastic Common Schema (ECS) 格式字段的 JSON 结构日志文件&#xff0c;然后你将在 Kibana 中查看日志事件发生的实时可视化。虽然此示例使…

SCI二区TOP|麋鹿群优化算法: 一种新颖的受自然启发的元启发式算法

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献5.代码获取 1.背景 2024年&#xff0c;SO Oladejo受到麋鹿群的繁殖过程启发&#xff0c;提出了麋鹿群优化算法&#xff08;Elk herd optimizer, EHO&#xff09;。 2.算法原理 2.1算法思想 EHO灵感来自麋鹿…

Win11右键默认显示更多选项的方法

问题描述 win11系统默认右键菜单显示选项太少&#xff0c;每次需要点一下“显示更多选项”才能得到想要内容。比方说我用notepad打开一个文档&#xff0c;在win11上要先点一下"显示更多选项“&#xff0c;再选择用notepad打开&#xff0c;操作非常反人类。 Win11右键默…

FreeRTOS 任务

FreeRTOS使用多任务完成功能&#xff0c;首先要了解任务的基本知识。 裸机编程中&#xff0c;单任务系统的编程方式&#xff0c;即裸机的编程方式&#xff0c;这种编程方式的框架一般都是在 main()函数 中使用一个大循环&#xff0c;在循环中顺序地调用相应的函数以处理相应的…