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

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

目录

类不能被拷贝

类不能被继承

只在堆上创建对象

只在栈上创建对象

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

相关文章

vscode保存代码时自动eslint格式化图文教程

《vscode保存代码时自动eslint格式化图文教程》:本文主要介绍vscode保存代码时自动eslint格式化的相关资料,包括打开设置文件并复制特定内容,文中通过代码介绍的非常详细,需要的朋友... 目录1、点击设置2、选择远程--->点击右上角打开设置3、会弹出settings.json文件,将以下内

Java循环创建对象内存溢出的解决方法

《Java循环创建对象内存溢出的解决方法》在Java中,如果在循环中不当地创建大量对象而不及时释放内存,很容易导致内存溢出(OutOfMemoryError),所以本文给大家介绍了Java循环创建对象... 目录问题1. 解决方案2. 示例代码2.1 原始版本(可能导致内存溢出)2.2 修改后的版本问题在

SQL Server使用SELECT INTO实现表备份的代码示例

《SQLServer使用SELECTINTO实现表备份的代码示例》在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误,在SQLServer中,可以使用SELECTINT... 在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误。在 SQL Server 中,可以使用 SE

python实现pdf转word和excel的示例代码

《python实现pdf转word和excel的示例代码》本文主要介绍了python实现pdf转word和excel的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录一、引言二、python编程1,PDF转Word2,PDF转Excel三、前端页面效果展示总结一

在MyBatis的XML映射文件中<trim>元素所有场景下的完整使用示例代码

《在MyBatis的XML映射文件中<trim>元素所有场景下的完整使用示例代码》在MyBatis的XML映射文件中,trim元素用于动态添加SQL语句的一部分,处理前缀、后缀及多余的逗号或连接符,示... 在MyBATis的XML映射文件中,<trim>元素用于动态地添加SQL语句的一部分,例如SET或W

使用C#代码计算数学表达式实例

《使用C#代码计算数学表达式实例》这段文字主要讲述了如何使用C#语言来计算数学表达式,该程序通过使用Dictionary保存变量,定义了运算符优先级,并实现了EvaluateExpression方法来... 目录C#代码计算数学表达式该方法很长,因此我将分段描述下面的代码片段显示了下一步以下代码显示该方法如

Python中的可视化设计与UI界面实现

《Python中的可视化设计与UI界面实现》本文介绍了如何使用Python创建用户界面(UI),包括使用Tkinter、PyQt、Kivy等库进行基本窗口、动态图表和动画效果的实现,通过示例代码,展示... 目录从像素到界面:python带你玩转UI设计示例:使用Tkinter创建一个简单的窗口绘图魔法:用

python多进程实现数据共享的示例代码

《python多进程实现数据共享的示例代码》本文介绍了Python中多进程实现数据共享的方法,包括使用multiprocessing模块和manager模块这两种方法,具有一定的参考价值,感兴趣的可以... 目录背景进程、进程创建进程间通信 进程间共享数据共享list实践背景 安卓ui自动化框架,使用的是

SpringBoot生成和操作PDF的代码详解

《SpringBoot生成和操作PDF的代码详解》本文主要介绍了在SpringBoot项目下,通过代码和操作步骤,详细的介绍了如何操作PDF,希望可以帮助到准备通过JAVA操作PDF的你,项目框架用的... 目录本文简介PDF文件简介代码实现PDF操作基于PDF模板生成,并下载完全基于代码生成,并保存合并P

SpringBoot基于MyBatis-Plus实现Lambda Query查询的示例代码

《SpringBoot基于MyBatis-Plus实现LambdaQuery查询的示例代码》MyBatis-Plus是MyBatis的增强工具,简化了数据库操作,并提高了开发效率,它提供了多种查询方... 目录引言基础环境配置依赖配置(Maven)application.yml 配置表结构设计demo_st