【 C++ 】特殊类设计

1、请设计一个类,不能被拷贝

拷贝只会出现在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。在C++98和C++11都有相对应的方法来解决此问题,下面我们分别讨论。

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

class CopyBan
{
public://……
private://只声明不定义CopyBan(const CopyBan&);//拷贝构造CopyBan& operator=(const CopyBan&);//赋值运算符重载
};

原因:

  • 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就不能禁止拷贝了。
  • 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。

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

class CopyBan
{CopyBan(const CopyBan&) = delete;//删除拷贝构造CopyBan& operator=(const CopyBan&) = delete;//删除赋值运算符重载
};

库里面如下都是经典的防拷贝:

  1. unique_ptr。
  2. thread线程 。
  3. mutex锁 。
  4. istream 。
  5. ostream。

2、请设计一个类,不能被继承

C++98:将该类的构造函数私有化即可,因为子类的构造函数被调用时,必须调用父类的构造函数初始化父类的那一部分成员,但无论是何种继承方式,父类的私有成员在子类是不可见的,所以创建子类对象时子类就无法调用父类的构造函数对父类的成员初始化,继而该类无法被继承。

class NonInherit
{
public:static NonInherit GetInstance(){return NonInherit();}
private:NonInherit()//私有化构造函数{}
};
  • C++98的这种方式其实不够彻底,因为这个类仍然可以被继承(编译器不会报错),只不过被继承后无法实例化出对象而已。因此我们推出C++11的方法。

C++11:使用final关键字,final修饰类,表示该类不能被继承。此时就算继承后没有创建对象也会编译出错。

class A final
{// ....
};

C++98是委婉的不能让你继承,C++11是直接的不能让你继承。

3、请设计一个类,只能在堆上创建对象

像我们平时创建对象,常见有如下三种在不同区域创建对象的方式:

class HeapOnly
{//……
};
int main()
{HeapOnly h1;static HeapOnly h2;HeapOnly* h3 = new HeapOnly;return 0;
}

既然只能在堆上创建对象,也就是只能通过new操作创建对象,有如下两种方式。

法一:

  • 将类的构造函数私有,拷贝构造声明成私有,防止别人调用拷贝在栈上生成对象。
  • 提供一个静态的成员函数,在该静态成员函数中完成对象的创建。
class HeapOnly
{
public://静态成员函数完成对象的创建static HeapOnly* CreateObj(){return new HeapOnly;}
private://构造函数私有HeapOnly(){}//防拷贝//C++98,只声明不实现,且声明成私有HeapOnly(const HeapOnly&);//C++11HeapOnly(const HeapOnly&) = delete;
};
int main()
{HeapOnly* ph1 = HeapOnly::CreateObj();HeapOnly* ph2 = HeapOnly::CreateObj();delete ph1;delete ph2;//HeapOnly h1;//栈-错误//static HeapOnly h2;//静态区-错误//HeapOnly copy(*ph2);//调用拷贝生成对象,在栈区,错误return 0;
}

注意:

  • 我们没有必要对赋值运算符重载设置为私有&&只声明不实现或者加上=delete(禁掉),因为赋值运算符重载是两个已经存在的对象,既然已经存在,那势必这俩对象就已经在堆区创建好了,所以它们之间进行赋值操作并不会出错。除非你不想用,那你可以把赋值运算符重载给禁掉。
  • 而拷贝构造是拿一个已经存在的对象去构造一个对象,此对象是先前未存在的,且拷贝构造后是在栈上的,自然不符合题意,因此需要把拷贝构造给禁掉,而赋值运算符重载不需要禁掉。

法二:

  • 将析构函数私有化。
class HeapOnly
{
public:
private://析构函数私有化~HeapOnly(){}
};
int main()
{//HeapOnly ph1;err//static HeapOnly ph2;errHeapOnly* ph3 = new HeapOnly;return 0;
}

为何析构函数私有化就能确保只能在堆上创建对象呢?

  • C++是一个静态绑定的语言。在编译过程中,所有的非虚函数调用都必须分析完成。即使是虚函数,也需检查可访问性。因此, 当在栈上生成对象时,对象会自动调用析构函数释放对象,也就说析构函数必须可以访问 ,否则编译出错。而在堆上生成对象,由于析构函数由程序员调用(通过使用delete),所以不一定需要析构函数。

既然析构函数私有化,如何delete你new出的资源呢?

  • 因为delete操作会调用析构函数,而析构函数已经被置为私有了,那就无法调用,为了解决此问题,我们只需要在类的内部提供一个静态成员函数,既然你类外不能调用私用成员,但是类里是可以调用的,因此我们在此成员函数中调用析构函数完成delete操作。
class HeapOnly
{
public://静态成员函数释放new的对象static void DelObj(HeapOnly* ptr){delete ptr;}
private://析构函数私有化~HeapOnly(){}
};
int main()
{HeapOnly* ph3 = new HeapOnly;//释放ph3HeapOnly::DelObj(ph3);return 0;
}

当然,我也可以使用delete this来释放new出的资源:

class HeapOnly
{
public:void DelObj(){delete this;}
private://析构函数私有化~HeapOnly(){}
};
int main()
{HeapOnly* ph3 = new HeapOnly;//释放ph3ph3->DelObj();return 0;
}

delete this–对象请求自杀,执行后不能再访问this指针。换句话说,你不能去检查它、将它和其他指针比较、和 NULL比较、打印它、转换它,以及其它的任何事情。不是很推荐这种方式。

4、请设计一个类,只能在栈上创建对象

法一:

  • 将构造函数设为私有,防止外部直接调用构造函数在堆上创建对象。
  • 提供静态成员函数,内部调用私有的构造函数完成对象的创建。
class StackOnly
{
public://静态成员函数,内部调用构造函数创建对象static StackOnly CreateObj(){return StackOnly();//传值返回 —— 拷贝构造}
private://构造函数私有StackOnly(){}
};
int main()
{StackOnly h1 = StackOnly::CreateObj();//static StackOnly h2; 错误//StackOnly* h3 = new StackOnly; 错误return 0;
}

此法有一缺陷,无法避免外部调用拷贝构造函数在静态区、堆区……创建对象。

int main()
{StackOnly h1 = StackOnly::CreateObj();//栈区static StackOnly h2(h1);//调用拷贝构造在静态区创建对象StackOnly* h3 = new StackOnly(h1);//调用拷贝构造在堆区创建对象return 0;
}

法二:

  • 把构造函数设为公有。
  • 屏蔽operator new函数和operator delete函数。
class StackOnly
{
public:StackOnly(){}
private://C++98void* operator new(size_t size);void operator delete(void* p);//C++11//void* operator new(size_t size) = delete;//void operator delete(void* p) = delete;
};
int main()
{StackOnly h1;//StackOnly* h3 = new StackOnly(h1);不能使用new在堆区创建对象return 0;
}

new和delete默认调用的是全局的operator new函数和operator delete函数,但如果一个类重载了专属的operator new函数和operator delete函数,那么new和delete就会调用这个专属的函数。所以只要把operator new函数和operator delete函数屏蔽掉,那么就无法再使用new在堆上创建对象了。

上述做法虽然成功避免了在堆区创建对象,但是无法避免在静态区或全局创建对象。

class StackOnly
{
private:void* operator new(size_t size) = delete;void operator delete(void* p) = delete;
};
StackOnly h1;//全局区
int main()
{static StackOnly h2;//静态区return 0;
}

综上,其实无论是法一还是法二多少都会存在点瑕疵,总是会有老六的出现,只能说是不那么严谨的情况下来看,法一算是ok的。

5、请设计一个类,只能创建一个对象(单例模式)

设计模式:

  • 设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。
  • 使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

现在已经总结出了23种设计模式:

  • 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
  • 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
  • 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

其中用的最多的就是单例模式。下面来展开讨论。

单例模式:一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

单例模式有两种实现模式:饿汉模式和懒汉模式。下面展开讨论。

饿汉模式

  • 饿汉模式就是不管你将来用不用,程序启动时就创建一个唯一的实例对象。

实现方式如下:

  • 将构造函数私有化,并将拷贝构造和拷贝赋值设为私有或删除,防止外部随意创建对象或拷贝。
  • 在类里创建一个static静态对象的指针,在进入程序入口之前就完成单例对象的初始化。
  • 提供一个static静态成员函数,用来获取单例对象的指针。
  • 将拷贝构造和拷贝赋值私有化,防止类外调用拷贝构造创建对象。
class Singleton
{
public://静态成员函数内部获取单例对象的指针static Singleton* GetInstance(){return _spInst;}void print();
private:Singleton(){}//C++98 防拷贝Singleton(const Singleton&);Singleton& operator=(Singleton const&);//C++11 防拷贝//Singleton(const Singleton&) = delete;//Singleton& operator=(Singleton const&) = delete;static Singleton* _spInst;//声明int _a = 0;
};
Singleton* Singleton::_spInst = new Singleton;//定义,在程序入口之前就完成单例对象的初始化
void Singleton::print()
{cout << _a << endl;
}
int main()
{Singleton::GetInstance()->print();//Singleton st1;err//Singleton* st2 = new Singleton;err//Singleton copy(*Singleton::GetInstance());errreturn 0;
}

再比如我现在有一个信息管理的类,需要保证进程里只有一份这样的信息,那么就需要把它设定为单例,整体框架和上面差不多其实,具体实现细节有所变动罢了:

//InfoMgr —— 单例
class InfoMgr
{
public://静态成员函数获取单例对象指针static InfoMgr* GetInstacne(){return _spInst;}//修改信息void SetAddress(const string& s){_address = s;}//获取信息string& GetAddress(){return _address;}
private://构造函数私有化InfoMgr()	{}//删除拷贝构造InfoMgr(const InfoMgr&) = delete;string _address;int _secretKey;static InfoMgr* _spInst;//声明
};
InfoMgr* InfoMgr::_spInst = new InfoMgr;//定义
int main()
{//全局只有一个InfoMgr对象InfoMgr::GetInstacne()->SetAddress("江苏省南京市");cout << InfoMgr::GetInstacne()->GetAddress() << endl;//江苏省南京市return 0;
}

如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。

懒汉模式

如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。

还是以上述信息管理的类为例,懒汉模式的实现方式如下:

  • 将构造函数置为私有,并将拷贝构造函数和赋值运算符重载函数设为私有或删除,防止外部创建或拷贝对象。
  • 提供一个指向单例对象的static指针,并在程序入口之前先将其初始化为空。
  • 提供一个static静态成员函数,只有当static指针为空时才初始化(也就是第一次调用此成员函数才创建对象),最后返回单例对象的指针。
  • 将拷贝构造和拷贝赋值私有化,防止类外调用拷贝构造创建对象。
//懒汉 -- 一开始不创建对象,第一次调用GetInstacne再创建对象
class InfoMgr
{
public://静态成员函数获取单例对象指针static InfoMgr* GetInstacne(){if (_spInst == nullptr){_spInst = new InfoMgr;}return _spInst;}//修改信息void SetAddress(const string& s){_address = s;}//获取信息string& GetAddress(){return _address;}
private://构造函数私有化InfoMgr(){}//删除拷贝构造InfoMgr(const InfoMgr&) = delete;string _address;int _secretKey;static InfoMgr* _spInst;//声明
};
InfoMgr* InfoMgr::_spInst = nullptr;//定义
int main()
{//全局只有一个InfoMgr对象InfoMgr::GetInstacne()->SetAddress("江苏省南京市");cout << InfoMgr::GetInstacne()->GetAddress() << endl;//江苏省南京市return 0;
}

懒汉模式这样写是有问题的,还需要加锁(双检查加锁),饿汉模式不需要加锁。

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

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

相关文章

ssm172旅行社管理系统的设计与实现

** &#x1f345;点赞收藏关注 → 私信领取本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345;** 一 、设计说明 1.1 研究…

day03-Vue-Element

一、Ajax 1 Ajax 介绍 1.1 Ajax 概述 概念&#xff1a;Asynchronous JavaScript And XML&#xff0c;异步 的 JavaScript 和 XML。 作用&#xff1a; 数据交换&#xff1a;通过 Ajax 可以给服务器发送请求&#xff0c;并获取服务器响应的数据。异步交互&#xff1a;可以在 不…

Java教程:SpringBoot项目如何对接Nacos实现服务发现治理,配置管理

–Nacos大家都知道&#xff0c;不懂的可以去官网或者网上查阅一下&#xff0c;本次给大家讲解一下如何在SpringBoot项目中引入Nacos服务来进行服务治理与发现&#xff0c;配置管理等&#xff0c;在微服务当中是必不可少的&#xff0c;各个模块之间可以通过Feign远程调用&#x…

物联网主机:为智能交通赋能

物联网&#xff08;IoT&#xff09;技术的发展为智能交通领域带来了许多创新的解决方案。而在物联网应用中&#xff0c;物联网主机起着关键的作用。本文将为大家介绍一款名为E6000的物联网主机&#xff0c;它是一种多协议、多接口的物联网主机&#xff0c;为智能交通系统的建设…

antvX6 - Vue自定义节点,并实现多种画布操作,拖拽、缩放、连线、双击、检索等等

一、 首先 antv x6 分为两个版本 低版本和高版本 我这里是使用的2.0版本 并且搭配了相关插件 例如&#xff1a;画布的图形变换、地图等 个人推荐 2.0版本&#xff0c;高版本配置多&#xff0c;可使用相关插件多&#xff0c;但是文档描述小&#xff0c;仍在更新&#xff0c; 低…

小d和图片压缩

题目描述 小ddd和她对象小红去海洋馆玩了&#xff0c;但是由于小ddd拍照技术不好&#xff0c;他对象说把她拍的像嘎子&#xff01; 小ddd看了看&#xff0c;发现是小红最近长痘痘了&#xff0c;于是他为了讨小红开心&#xff0c;让痘痘看不见&#xff0c;自学了图像压缩这个技…

装饰器模式 详解 设计模式

装饰器模式 它允许你在不改变对象结构的情况下&#xff0c;动态地将新功能附加到对象上。 结构&#xff1a; 抽象组件&#xff08;Component&#xff09;&#xff1a;定义了原始对象和装饰器对象的公共接口或抽象类&#xff0c;可以是具体组件类的父类或接口。具体组件&…

固定排班计划

目录 1.按发车时间排序。 2.排班日期默认当天时间。 3.编辑不可修改线路和排班日期。 4.线路、车号、司机是否匹配&#xff0c;不匹配不可入库&#xff08;和其他表比&#xff09;&#xff0c;线路、发车时间、司机、车号、日期、上下行相同不可入库&#xff08;和自己表比…

GO语言学习笔记(与Java的比较学习)(一)

GO的优缺点&#xff1a; 此处引用华为云开发者联盟的一篇文章&#xff1a; GO语言的亮点很明显&#xff1a; GoDoc。 GoDoc的静态语言分析能力很强大&#xff0c;可以直接从代码和注释生成漂亮的文档。这一点区别于其他的类似工具如JavaDoc, PHPDoc或者JSDoc。这些工具需要添加…

如何在群晖Docker运行本地聊天机器人并结合内网穿透发布到公网访问

文章目录 1. 拉取相关的Docker镜像2. 运行Ollama 镜像3. 运行Chatbot Ollama镜像4. 本地访问5. 群晖安装Cpolar6. 配置公网地址7. 公网访问8. 固定公网地址 随着ChatGPT 和open Sora 的热度剧增,大语言模型时代,开启了AI新篇章,大语言模型的应用非常广泛&#xff0c;包括聊天机…

C# Socket通信从入门到精通(21)——TCP发送文件与接收文件 C#代码实现

1、前言 我们在开发上位机软件的过程中经常需要发送文件,本文就是介绍如何利用tcp客户端发送文件、tcp服务器端接收文件,而且本文介绍的方法可以自动发送一个文件夹下的所有子目录以及所有文件,经验来自于实际项目,具备非常有价值的参考意义! 2、发送文件以及C#代码 被发…

LeetCode第48天 买卖股票的最佳时机 买卖股票的最佳时机II 动态规划

121. 买卖股票的最佳时机 class Solution { public:int maxProfit(vector<int>& prices) {// int res 0 ;// int low INT_MAX;// for (int i 0; i < prices.size(); i) {// low min(low, prices[i]);// res max(res, prices[i]-low);// }// return r…

低密度奇偶校验码LDPC(八)——QC-LDPC译码器FPGA设计概要

往期博文 低密度奇偶校验码LDPC&#xff08;一&#xff09;——概述_什么是gallager构造-CSDN博客 低密度奇偶校验码LDPC&#xff08;二&#xff09;——LDPC编码方法-CSDN博客 低密度奇偶校验码LDPC&#xff08;三&#xff09;——QC-LDPC码概述-CSDN博客 低密度奇偶校验码…

Linux系统--------内核参数调优、一键安装nginx、tomcat调优

一、内核参数调优 默认的Linux内核参数考虑的是最通用场景&#xff0c;不符合用于支持高并发访问的Web服务器的定义&#xff0c;根据业务特点来进行调整&#xff0c;当Nginx作为静态web内容服务器、反向代理或者提供压缩服务器的服务器时&#xff0c;内核参数的调整都是不同的…

Spring面试系列-02

1. Spring 中自动装配有那些局限性? 自动装配的局限性 重写:仍需用<constructor-arg>和<property>配置来定义依赖,意味着总要重写自动装配。 基本数据类型:不能自动装配简单的属性,例如基本数据类型、String字符串、和类。 模糊特性:自动装配不如显式装配…

Vue点击复制到剪切板

一、Vue2写法 安装 &#xff08;官网地址&#xff09; npm install --save vue-clipboard2 使用 //main.js import VueClipboard from vue-clipboard2 Vue.use(VueClipboard)//页面使用 <button type"button"v-clipboard:copy"message"v-clipboard:su…

Mac电脑软件开发的优缺点

Mac电脑软件开发的优缺点 在软件开发领域&#xff0c;Mac电脑一直以其独特的优势占有一席之地。然而&#xff0c;就像任何工具或平台一样&#xff0c;Mac电脑在软件开发方面也存在其优点和缺点。本文将探讨在Mac上进行软件开发的利弊&#xff0c;帮助您了解是否应将Mac作为您的…

node.js 用 xml2js.Parser 读 Freeplane.mm文件,生成测试用例.csv文件

Freeplane 是一款基于 Java 的开源软件&#xff0c;继承 Freemind 的思维导图工具软件&#xff0c;它扩展了知识管理功能&#xff0c;在 Freemind 上增加了一些额外的功能&#xff0c;比如数学公式、节点属性面板等。 编写 mm_xml2js_csv.js 如下 // 用 xml2js.Parser 读 F…

Android 通过Intent打开第三方App

Android 使用 Intent 打开第三方应用或调用制定 Activity Intent intent new Intent(); intent.setClassName("package name", "activity name"); // 内部调用 intent.setComponent(new ComponentName("package name", "activity name&qu…

javaWebssh票据管理系统myeclipse开发mysql数据库MVC模式java编程计算机网页设计

一、源码特点 java ssh票据管理系统是一套完善的web设计系统&#xff08;系统采用ssh框架进行设计开发&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模 式开发。开发环境为TOMCAT7.0,My…