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

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

目录

类不能被拷贝

类不能被继承

只在堆上创建对象

只在栈上创建对象

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/288172

相关文章

C#实现千万数据秒级导入的代码

《C#实现千万数据秒级导入的代码》在实际开发中excel导入很常见,现代社会中很容易遇到大数据处理业务,所以本文我就给大家分享一下千万数据秒级导入怎么实现,文中有详细的代码示例供大家参考,需要的朋友可... 目录前言一、数据存储二、处理逻辑优化前代码处理逻辑优化后的代码总结前言在实际开发中excel导入很

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

Python实现Excel批量样式修改器(附完整代码)

《Python实现Excel批量样式修改器(附完整代码)》这篇文章主要为大家详细介绍了如何使用Python实现一个Excel批量样式修改器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录前言功能特性核心功能界面特性系统要求安装说明使用指南基本操作流程高级功能技术实现核心技术栈关键函

Redis实现高效内存管理的示例代码

《Redis实现高效内存管理的示例代码》Redis内存管理是其核心功能之一,为了高效地利用内存,Redis采用了多种技术和策略,如优化的数据结构、内存分配策略、内存回收、数据压缩等,下面就来详细的介绍... 目录1. 内存分配策略jemalloc 的使用2. 数据压缩和编码ziplist示例代码3. 优化的

Python 基于http.server模块实现简单http服务的代码举例

《Python基于http.server模块实现简单http服务的代码举例》Pythonhttp.server模块通过继承BaseHTTPRequestHandler处理HTTP请求,使用Threa... 目录测试环境代码实现相关介绍模块简介类及相关函数简介参考链接测试环境win11专业版python

Python从Word文档中提取图片并生成PPT的操作代码

《Python从Word文档中提取图片并生成PPT的操作代码》在日常办公场景中,我们经常需要从Word文档中提取图片,并将这些图片整理到PowerPoint幻灯片中,手动完成这一任务既耗时又容易出错,... 目录引言背景与需求解决方案概述代码解析代码核心逻辑说明总结引言在日常办公场景中,我们经常需要从 W

使用Spring Cache本地缓存示例代码

《使用SpringCache本地缓存示例代码》缓存是提高应用程序性能的重要手段,通过将频繁访问的数据存储在内存中,可以减少数据库访问次数,从而加速数据读取,:本文主要介绍使用SpringCac... 目录一、Spring Cache简介核心特点:二、基础配置1. 添加依赖2. 启用缓存3. 缓存配置方案方案

MySQL的配置文件详解及实例代码

《MySQL的配置文件详解及实例代码》MySQL的配置文件是服务器运行的重要组成部分,用于设置服务器操作的各种参数,下面:本文主要介绍MySQL配置文件的相关资料,文中通过代码介绍的非常详细,需要... 目录前言一、配置文件结构1.[mysqld]2.[client]3.[mysql]4.[mysqldum

Python多线程实现大文件快速下载的代码实现

《Python多线程实现大文件快速下载的代码实现》在互联网时代,文件下载是日常操作之一,尤其是大文件,然而,网络条件不稳定或带宽有限时,下载速度会变得很慢,本文将介绍如何使用Python实现多线程下载... 目录引言一、多线程下载原理二、python实现多线程下载代码说明:三、实战案例四、注意事项五、总结引

IDEA与MyEclipse代码量统计方式

《IDEA与MyEclipse代码量统计方式》文章介绍在项目中不安装第三方工具统计代码行数的方法,分别说明MyEclipse通过正则搜索(排除空行和注释)及IDEA使用Statistic插件或调整搜索... 目录项目场景MyEclipse代码量统计IDEA代码量统计总结项目场景在项目中,有时候我们需要统计