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

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

目录

类不能被拷贝

类不能被继承

只在堆上创建对象

只在栈上创建对象

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

相关文章

Java集合之Iterator迭代器实现代码解析

《Java集合之Iterator迭代器实现代码解析》迭代器Iterator是Java集合框架中的一个核心接口,位于java.util包下,它定义了一种标准的元素访问机制,为各种集合类型提供了一种统一的... 目录一、什么是Iterator二、Iterator的核心方法三、基本使用示例四、Iterator的工

Java 线程池+分布式实现代码

《Java线程池+分布式实现代码》在Java开发中,池通过预先创建并管理一定数量的资源,避免频繁创建和销毁资源带来的性能开销,从而提高系统效率,:本文主要介绍Java线程池+分布式实现代码,需要... 目录1. 线程池1.1 自定义线程池实现1.1.1 线程池核心1.1.2 代码示例1.2 总结流程2. J

JS纯前端实现浏览器语音播报、朗读功能的完整代码

《JS纯前端实现浏览器语音播报、朗读功能的完整代码》在现代互联网的发展中,语音技术正逐渐成为改变用户体验的重要一环,下面:本文主要介绍JS纯前端实现浏览器语音播报、朗读功能的相关资料,文中通过代码... 目录一、朗读单条文本:① 语音自选参数,按钮控制语音:② 效果图:二、朗读多条文本:① 语音有默认值:②

Vue实现路由守卫的示例代码

《Vue实现路由守卫的示例代码》Vue路由守卫是控制页面导航的钩子函数,主要用于鉴权、数据预加载等场景,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着... 目录一、概念二、类型三、实战一、概念路由守卫(Navigation Guards)本质上就是 在路

uni-app小程序项目中实现前端图片压缩实现方式(附详细代码)

《uni-app小程序项目中实现前端图片压缩实现方式(附详细代码)》在uni-app开发中,文件上传和图片处理是很常见的需求,但也经常会遇到各种问题,下面:本文主要介绍uni-app小程序项目中实... 目录方式一:使用<canvas>实现图片压缩(推荐,兼容性好)示例代码(小程序平台):方式二:使用uni

JAVA实现Token自动续期机制的示例代码

《JAVA实现Token自动续期机制的示例代码》本文主要介绍了JAVA实现Token自动续期机制的示例代码,通过动态调整会话生命周期平衡安全性与用户体验,解决固定有效期Token带来的风险与不便,感兴... 目录1. 固定有效期Token的内在局限性2. 自动续期机制:兼顾安全与体验的解决方案3. 总结PS

C#中通过Response.Headers设置自定义参数的代码示例

《C#中通过Response.Headers设置自定义参数的代码示例》:本文主要介绍C#中通过Response.Headers设置自定义响应头的方法,涵盖基础添加、安全校验、生产实践及调试技巧,强... 目录一、基础设置方法1. 直接添加自定义头2. 批量设置模式二、高级配置技巧1. 安全校验机制2. 类型

Python屏幕抓取和录制的详细代码示例

《Python屏幕抓取和录制的详细代码示例》随着现代计算机性能的提高和网络速度的加快,越来越多的用户需要对他们的屏幕进行录制,:本文主要介绍Python屏幕抓取和录制的相关资料,需要的朋友可以参考... 目录一、常用 python 屏幕抓取库二、pyautogui 截屏示例三、mss 高性能截图四、Pill

使用MapStruct实现Java对象映射的示例代码

《使用MapStruct实现Java对象映射的示例代码》本文主要介绍了使用MapStruct实现Java对象映射的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、什么是 MapStruct?二、实战演练:三步集成 MapStruct第一步:添加 Mave

Java抽象类Abstract Class示例代码详解

《Java抽象类AbstractClass示例代码详解》Java中的抽象类(AbstractClass)是面向对象编程中的重要概念,它通过abstract关键字声明,用于定义一组相关类的公共行为和属... 目录一、抽象类的定义1. 语法格式2. 核心特征二、抽象类的核心用途1. 定义公共接口2. 提供默认实