特殊类设计(只在堆/栈上创建对象,单例模式),完整版代码+思路

本文主要是介绍特殊类设计(只在堆/栈上创建对象,单例模式),完整版代码+思路,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

类不能被拷贝

类不能被继承

只在堆上创建对象

只在栈上创建对象

operator new

operator delete

只能创建一个对象

设计模式

介绍

常见的设计模式

单例模式

介绍

应用

饿汉模式

介绍

实现

思路

代码

使用

懒汉模式

引入

介绍

实现

思路

代码

使用

显式析构

隐式析构


类不能被拷贝

拷贝有两种方式,拷贝构造和赋值拷贝,所以只需要让创建出的对象不能使用这两个成员函数即可

"不能使用"可以有两种方式

  • 设置为私有
  • 私有后,一般只声明不定义,如果定义了,可能会在类内部使用拷贝操作
  • 使用关键字"delete"
  • c++11后扩展了delete的用法
  • delete除了释放new申请的资源外,如果在默认成员函数后跟上
    =delete,表示让编译器删除掉该默认成员函数
  • 既然已经删除了,也就无法使用了

类不能被继承

之前就有提到过,如果构造函数是私有的,则无法创建对象

那在继承概念中,如果基类无法被创建,自然子类也创建不出来,也就没有继承的说法了

也可以使用c++11引入的final关键字,表示该类不能被继承

只在堆上创建对象

  • 也就是说,只能通过申请资源的方式创建对象,那么申请到的都是由指针指向的一块空间,也就是说,需要我们返回指针
  • 但构造函数显然无法满足这个条件,所以我们干脆禁用构造函数,直接给一个接口函数
  • 禁用就和前面不能拷贝类似,有两种方式

  • 注意!!!要把这个接口函数设置成静态的
  • 这个函数本来就是用来创建对象的,但普通成员函数的调用又需要一个对象,这就形成了先有鸡还是先有蛋的问题
  • 所以,直接设置成静态的,就可以用类域调用了
class HeapOnly
{
public:static HeapOnly* CreateObject() //一定要是静态的嗷!!!{return new HeapOnly;}
private:// C++98// 1.只声明,不实现,本身就不需要实现// 2.声明成私有HeapOnly() {}HeapOnly(const HeapOnly&);C++11    //HeapOnly(const HeapOnly&) = delete;
};

只在栈上创建对象

也就是要禁止申请空间,首先可以采用上面的方式,直接给接口,构造禁掉

class StackOnly
{
public:static StackOnly CreateObject(){return StackOnly();}
private:StackOnly() {}StackOnly(const StackOnly&);
};

类对象在new的时候,实际上会先调用operator new,然后再调用构造函数

operator new

  • 是C++中用于动态分配内存的内置运算符
  • 主要作用是分配一块连续的内存空间,以便在其中存储对象或数据
  • 可以被重载

所以我们可以声明一个删除的operator new函数,这样外部就不能用new申请资源了

class StackOnly
{
public:StackOnly(){}StackOnly(const StackOnly& tmp) {}
private:void* operator new(size_t size) = delete;int _a = 1;
};

还可以禁掉释放资源的函数,这样申请到的资源也就无法释放,那编译器就不会允许你在堆上申请

还记得前面的operator new吗,类似的,delete也会先调用operator delete

operator delete

  • 和operator new配套的运算符,用于释放动态分配内存的内置运算符
  • 通过传递要释放的内存块的指针,它将该内存块返回给系统或内存管理器,以便将其重新分配给其他用途
  • 允许重载

所以,和上面操作类似

class StackOnly
{
public:StackOnly(){}StackOnly(const StackOnly& tmp) {}
private://void* operator new(size_t size) = delete;void operator delete(void* p) = delete;int _a = 1;
};

只能创建一个对象

设计模式

介绍

  • 设计模式是一种用于解决软件设计问题的经验和最佳实践的复用方案
  • 它们提供了在特定情境下的通用解决方案,有助于创建更可维护、灵活和可扩展的软件
  • 设计模式是从实践中总结出来的,并被广泛接受和使用,以解决常见的设计问题

常见的设计模式

这里只介绍一下单例模式

单例模式

介绍

单例模式是一种创建型设计模式,其目的是确保一个类只有一个实例,并提供一个全局访问点来访问该实例

应用
  • 当需要共享某个资源,例如配置信息、日志记录、数据库连接、线程池等,单例模式可以确保全局只有一个资源实例,避免资源的浪费和冲突
  • 当需要维护全局状态,例如应用程序的状态或设置,单例模式可以提供一个中心点来管理和访问这些状态
  • 也就是程序运行过程中,只需要一份/只能有一份的时候,单例模式可以防止创建出多份对象

饿汉模式

介绍
  • 也称为预先实例化模式,是一种单例模式的实现方式
  • 在饿汉模式中,单例实例在类加载时就被创建,因此在整个程序生命周期中,该实例都是唯一的
实现
思路
  • 因为只能有一个,所以构造/拷贝构造/赋值拷贝都必须禁用
  • 为了确保只有一份实例,我们的接口函数必须返回的是同一个对象
  • 并且这个对象应该是静态的,不然怎么实现返回的都是一个对象,必须要让它的作用域是全局,而不是某个对象
  • 由于是预先实例化,所以提前在程序开始前实例化,也就是在全局中实例化
代码
class Singleton {
public:static Singleton& GetInstance() { //每次调用接口都只返回那一个对象return _instance;}void add(int t) {_arr.push_back(t);}void print() {for (auto t : _arr) {std::cout << t << " ";}}
private:Singleton() {}; //注意这里要有函数体(也就是要定义它),因为我们的_instance需要被初始化Singleton(const Singleton&);Singleton& operator=(const Singleton&);static Singleton _instance;std::vector<int> _arr;
};
Singleton Singleton::_instance; 
使用
int main() {Singleton::GetInstance().add(1);//使用类域调用对象接口,并且用返回值直接调用函数,因为对象是私有的,不能被显式定义Singleton::GetInstance().add(2);Singleton::GetInstance().add(3);Singleton::GetInstance().print();return 0;
}

懒汉模式

引入
  • 饿汉模式存在很多缺陷,如果类很大,那程序启动所需的时间将会很长
  • 或者如果某个类需要依赖其他类,但我们无法保证究竟哪个类先被实例化
  • 所以,为了解决这些问题,懒汉模式就被引入了
介绍
  • 懒汉模式推迟了实例的创建,直到首次访问该实例时才进行初始化
  • 避免在程序启动时需要大量资源初始化时,产生不必要的开销
  • 但是,在多线程环境下不是线程安全的
实现
思路
  • 既然需要在首次访问时创建对象,那么就在第一次调用接口时才创建对象,之后返回该对象即可
  • 该如何判断是否为第一次呢?
  • 可以考虑用一个指针,如果该指针有指向的对象,就说明不是第一次了
  • 但是,这个对象必须得是动态开辟出来的,不然构造出来的是个右值对象,没法取地址
  • 所以,就要面临释放资源的问题
  • 但是,由于我们的变量是个指针,没法自动析构,所以要定义一个接口del来手动释放
代码
namespace lazy {class Singleton {public:static Singleton& GetInstance() { //每次调用接口都只返回那一个对象if (_p==nullptr) {//_p = &Singleton(); //如果_p指向普通对象,这里就是右值了,无法取地址_p = new Singleton;//所以必须要动态申请,这样的话就需要释放资源了//但其实一般单例模式不需要释放,随程序结束就自己释放了//但我们可能中途需要释放,那析构函数就没有用了,_p是静态对象,不会在中途自己调用析构//所以需要定义一个接口}return *_p;}static void DelInstance() { //因为我们的对象是私有的,在外部访问不到,定义成static方便一点if (_p) {delete _p;//这里会调用析构_p = nullptr;}}void add(int t) {_arr.push_back(t);}void print() {for (auto t : _arr) {std::cout << t << " ";}}private:Singleton() {};Singleton(const Singleton&);Singleton& operator=(const Singleton&);~Singleton() //_p离开作用域并不会调用析构,所以需要手动调用,也就是在del接口中{std::cout << "~Singleton()" << std::endl;//其他操作}std::vector<int> _arr;static Singleton* _p;//和_instance一样,要定义成静态的,保证_p的唯一性};Singleton* Singleton::_p = nullptr;}
使用
  • 使用其实和饿汉模式差不多,只不过懒汉的构造调用时间不同,并且增加了释放资源的接口
  • 注意!!!因为我们定义的是个指针,所以不会自动析构
  • 所以,唯一的接口就是那个del,在它里面可以调用析构
  • 我们可以手动调用del
  • 也可以改造一下让他具有析构函数的性质(离开作用域自动调用)
显式析构

像上面那样写,可以让我们显式调用del函数来释放资源,并且在释放前完成某些操作

int main() {lazy::Singleton::GetInstance().add(1); lazy::Singleton::GetInstance().add(2);lazy::Singleton::GetInstance().add(3);lazy::Singleton::GetInstance().print();lazy::Singleton::DelInstance();//显式调用lazy::Singleton::GetInstance().add(4);lazy::Singleton::GetInstance().print();return 0;
}

注意,这里最后程序没有自动调用析构,因为我们的静态成员是_p指针,它来指向被申请的空间,而不是直接创建一个对象

隐式析构
  • 如果我们想要在程序结束前也完成某些操作,就可能不太方便,需要我们手动调用接口,况且我们可能也不知道什么时候程序结束
  • 所以,可以利用智能指针的特性,将del接口放在某个类的析构函数中
  • 这样随着类析构,也就自动调用了del函数
  • namespace lazy {class Singleton {public:static Singleton& GetInstance() { //每次调用接口都只返回那一个对象if (_p==nullptr) {//_p = &Singleton(); //如果_p指向普通对象,这里就是右值了,无法取地址_p = new Singleton;//所以必须要动态申请,这样的话就需要释放资源了//但其实一般单例模式不需要释放,随程序结束就自己释放了//但我们可能中途需要释放,那析构函数就没有用了,_p是静态对象,不会在中途自己调用析构//所以需要定义一个接口}return *_p;}static void DelInstance() { //因为我们的对象是私有的,在外部访问不到,定义成static方便一点if (_p) {delete _p;//这里会调用析构_p = nullptr;}}class func { //用于程序结束自动调用del接口public:~func() {Singleton::DelInstance();}};void add(int t) {_arr.push_back(t);}void print() {for (auto t : _arr) {std::cout << t << " ";}}private:Singleton() {}; //注意这里要有函数体(也就是要定义它),因为我们的_instance需要被初始化Singleton(const Singleton&);Singleton& operator=(const Singleton&);~Singleton() //vs2019下,静态对象好像不会自动调析构,而是直接释放资源了{std::cout << "~Singleton()" << std::endl;//其他操作}std::vector<int> _arr;static Singleton* _p;static func _f;//定义一个静态对象//得是静态的,不然一个对象一个_f,会对_p析构多次};Singleton* Singleton::_p = nullptr;Singleton::func Singleton::_f; 
    }
  • 这样,即使我们没有显式调用del,也可以在程序结束前自动调用del

这篇关于特殊类设计(只在堆/栈上创建对象,单例模式),完整版代码+思路的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/290081

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

poj 1258 Agri-Net(最小生成树模板代码)

感觉用这题来当模板更适合。 题意就是给你邻接矩阵求最小生成树啦。~ prim代码:效率很高。172k...0ms。 #include<stdio.h>#include<algorithm>using namespace std;const int MaxN = 101;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int n

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

怎么让1台电脑共享给7人同时流畅设计

在当今的创意设计与数字内容生产领域,图形工作站以其强大的计算能力、专业的图形处理能力和稳定的系统性能,成为了众多设计师、动画师、视频编辑师等创意工作者的必备工具。 设计团队面临资源有限,比如只有一台高性能电脑时,如何高效地让七人同时流畅地进行设计工作,便成为了一个亟待解决的问题。 一、硬件升级与配置 1.高性能处理器(CPU):选择多核、高线程的处理器,例如Intel的至强系列或AMD的Ry

计算机毕业设计 大学志愿填报系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点赞 👍 收藏 ⭐评论 📝 🍅 文末获取源码联系 👇🏻 精彩专栏推荐订阅 👇🏻 不然下次找不到哟~Java毕业设计项目~热门选题推荐《1000套》 目录 1.技术选型 2.开发工具 3.功能

BUUCTF(34)特殊的 BASE64

使用pycharm时,如果想把代码撤销到之前的状态可以用 Ctrl+z 如果不小心撤销多了,可以用 Ctrl+Shift+Z 还原, 别傻傻的重新敲了 BUUCTF在线评测 (buuoj.cn) 查看字符串,想到base64的变表 这里用的c++的标准程序库中的string,头文件是#include<string> 这是base64的加密函数 std::string

代码随想录冲冲冲 Day39 动态规划Part7

198. 打家劫舍 dp数组的意义是在第i位的时候偷的最大钱数是多少 如果nums的size为0 总价值当然就是0 如果nums的size为1 总价值是nums[0] 遍历顺序就是从小到大遍历 之后是递推公式 对于dp[i]的最大价值来说有两种可能 1.偷第i个 那么最大价值就是dp[i-2]+nums[i] 2.不偷第i个 那么价值就是dp[i-1] 之后取这两个的最大值就是d

pip-tools:打造可重复、可控的 Python 开发环境,解决依赖关系,让代码更稳定

在 Python 开发中,管理依赖关系是一项繁琐且容易出错的任务。手动更新依赖版本、处理冲突、确保一致性等等,都可能让开发者感到头疼。而 pip-tools 为开发者提供了一套稳定可靠的解决方案。 什么是 pip-tools? pip-tools 是一组命令行工具,旨在简化 Python 依赖关系的管理,确保项目环境的稳定性和可重复性。它主要包含两个核心工具:pip-compile 和 pip