C++ -- 特殊类设计

目录

设计一个类,不能被拷贝

C++98的做法

C++11的做法

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

实现方式1

实现方式2

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

实现方式1

方式1的优化

实现方式2

设计一个类,不能被继承

设计模式

什么是设计模式?

单例模式

饿汉模式

懒汉模式 


设计一个类,不能被拷贝


        拷贝只会发生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可

C++98的做法

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

class CopyBan
{// ...
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

  1. 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
  2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
class HeapOnly {
public:static HeapOnly* GetObj() {return new HeapOnly;}//C++ 11防拷贝HeapOnly(const HeapOnly&) = delete;
private:HeapOnly() {}C++98防拷贝//HeapOnly(const HeapOnly&){}};int main() {//HeapOnly hp; // 报错//HeapOnly* p = new HeapOnly; // 报错HeapOnly* p = HeapOnly::GetObj();shared_ptr< HeapOnly> sp1(HeapOnly::GetObj());//HeapOnly p = *sp1; // 报错return 0;
}

为什么要加上static?

        如果CetObj不加上static,那么在调用该方法就需要在存在对象的基础上才能使用该方法,而该对象默认一定会用构造函数,但是构造函数已经私有化,这就是一个先有鸡还是先有蛋的问题,因此一定要加上static。

实现方式2

  1. 析构函数私有化
  2. 构造函数public显示调用
  3. 新增Destory方法,用来释放堆空间
// 析构私有化
class HeapOnly2
{
public:HeapOnly2(){}void Destory(){this->~HeapOnly2();}
private:~HeapOnly2(){}HeapOnly2(const HeapOnly2&) = delete;
};int main()
{//HeapOnly2 hp; // 报错HeapOnly2* p = new HeapOnly2;p->Destory();HeapOnly2* php1 = new HeapOnly2;php1->Destory();return 0;
}

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


实现方式1

  1. 将构造函数私有化。
  2. 然后设计静态方法创建对象返回即可。
class StackOnly {
public:static StackOnly GetObj() {return StackOnly();}private:StackOnly() {}
};
int main() {//StackOnly hp; // 报错//StackOnly* p = new StackOnly; // 报错StackOnly p = StackOnly::GetObj();//static StackOnly ss; // 报错StackOnly obj1 = StackOnly::GetObj();static StackOnly obj2(obj1); //可以在静态区拷贝构造对象StackOnly* ptr = new StackOnly(obj1); //可以在堆上拷贝构造对象return 0;
}

实际上,这种方法也没有彻底的封死,下面这种方式仍然可以在静态区和堆区创建:

StackOnly obj1 = StackOnly::GetObj();
static StackOnly obj2(obj1); //可以在静态区拷贝构造对象
StackOnly* ptr = new StackOnly(obj1); //可以在堆上拷贝构造对象

方式1的优化

因此,需要把拷贝构造封住,防止拷贝,而是直接调用 GetObj(),且 GetObj() 返回值只能是右值对象 如下:

class StackOnly3
{
public:static StackOnly3&& GetObj(){return StackOnly3();}void Print() const{cout << "StackOnly::Print()" << endl;}
private:StackOnly3(){}StackOnly3(const StackOnly3&) = delete;};
int main()
{	//StackOnly3 hp; // 报错//StackOnly3* p = new StackOnly3; // 报错//StackOnly3 so1 = StackOnly3::GetObj(); // 报错//static StackOnly3 so2 = StackOnly3::GetObj(); // 报错//StackOnly3 obj1 = StackOnly3::GetObj(); // 报错//static StackOnly3 obj2(obj1); //在静态区拷贝构造对象 报错//StackOnly3* ptr = new StackOnly3(obj1); //在堆上拷贝构造对象 报错//StackOnly3 p = StackOnly3::GetObj(); // 报错StackOnly3::GetObj().Print();const StackOnly3& so4 = StackOnly3::GetObj();so4.Print();return 0;
}

由于GetObj() 中StackOnly3()是局部对象,出了作用域被销毁,因此采用右值引用才可以传出。

右值引用做 GetObj() 返回值可以解决返回对象调用拷贝构造,因为它调用的是默认的移动构造

实现方式2

        屏蔽operator new函数和operator delete函数

//这种方案存在一定程序缺陷,无法阻止在数据段(静态区)创建对象
class StackOnly2 {
public://C++98//void* operator new(size_t size);//void operator delete(void* p);//C++11void* operator new(size_t size) = delete;void operator delete(void* p) = delete;
};int main(){//StackOnly2* p2 = new StackOnly2; //报错static StackOnly2 ss; //可以在静态区构造对象StackOnly2 p = StackOnly2();return 0;
}

这种方法只能封住堆上的,却无法封住静态的。

所以最好的方式就是用方式一。

设计一个类,不能被继承


  • C++98方式
// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
class NonInherit
{
public:static NonInherit GetInstance(){return NonInherit();}
private:NonInherit(){}
};
  • C++11方法

final关键字,final修饰类,表示该类不能被继承。

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

设计模式


什么是设计模式?

创建型模式(Creational Patterns):

  • 工厂方法模式(Factory Method Pattern)
  • 抽象工厂模式(Abstract Factory Pattern)
  • 单例模式(Singleton Pattern)
  • 建造者模式(Builder Pattern)
  • 原型模式(Prototype Pattern)

结构型模式(Structural Patterns):

  • 适配器模式(Adapter Pattern)
  • 桥接模式(Bridge Pattern)
  • 组合模式(Composite Pattern)
  • 装饰者模式(Decorator Pattern)
  • 外观模式(Facade Pattern)
  • 享元模式(Flyweight Pattern)
  • 代理模式(Proxy Pattern)
     

行为型模式(Behavioral Patterns):

  • 观察者模式(Observer Pattern)
  • 状态模式(State Pattern)
  • 策略模式(Strategy Pattern)
  • 命令模式(Command Pattern)
  • 职责链模式(Chain of Responsibility Pattern)
  • 迭代器模式(Iterator Pattern)
  • 中介者模式(Mediator Pattern)
  • 备忘录模式(Memento Pattern)
  • 访问者模式(Visitor Pattern)
  • 模板方法模式(Template Method Pattern)
     

并发型模式(Concurrent Patterns):

  • 信号量模式(Semaphore Pattern)
  • 线程池模式(Thread Pool Pattern)
  • 读写锁模式(Read-Write Lock Pattern)
  • 生产者消费者模式(Producer-Consumer Pattern)

以上仅是一些常见的设计模式,实际上还有其他的设计模式。每个设计模式都有特定的应用场景和解决问题的方式。请注意,在使用设计模式时,应根据具体的需求和情况来选择适当的设计模式。

比如迭代器模式,把复杂的东西给封装好,使用时就可以避免接触复杂的底层结构。

比如配接器模式等等,也是这个意思。

使用设计模式的目的: 为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
 

单例模式

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

由于全局对象只能有一个,换句话说是获取这个对象,那就需要对构造函数进行操作。

单例模式有两种实现模式:饿汉模式、懒汉模式

饿汉模式

饿汉模式的条件:main函数之前就初始化

设计饿汉模式的步骤:

  1. 将构造函数设成private,以及封死拷贝构造和重载赋值
  2. 定义成员变量,变量类型为static 类型名
  3. 在类外初始化这个单例的对象
  4. 添加其它成员方法
namespace Hungry_man {// 饿汉模式 一开始(main函数之前)就创建对象class Singleton {public:static Singleton* GetInstance() {return &_inst;}Singleton(const Singleton&) = delete;private:Singleton() {};static Singleton _inst; //静态对象};Singleton Singleton::_inst; //在程序入口之前就完成单例对象的初始化// static对象是在main函数之前创建的,这会只有主线程,所以不存在线程安全。int x() {//Singleton s1; // 报错//Singleton copy(*Singleton::GetInstance()); // 报错cout << Singleton::GetInstance() << endl;cout << Singleton::GetInstance() << endl;vector<std::thread > vthreads;int n = 16;for (int i = 0; i < n; ++i) {vthreads.push_back(std::thread([]() {//cout << std::this_thread::get_id() << ":";cout << Hungry_man::Singleton::GetInstance() << endl;}));}for (auto& e : vthreads){e.join();}return 0;}
}int main() {Hungry_man::x();return 0;
}

饿汉模式优缺点

优点:简单
缺点:单例对象初始化数据太多,可能会导致进程启动慢,且如果有多个单例类有初始化依赖关系,饿汉模式无法控制

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

线程安全相关问题:

饿汉模式在程序运行主函数之前就完成了单例对象的创建,由于线程是在main函数之后创建的,因此饿汉模式下单例对象的创建过程是线程安全的。

懒汉模式 

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

懒汉模式是需要用到才创建,并且在main函数之后​​​​​​​

  1. 将构造函数设置为私有,并将拷贝构造函数和赋值运算符重载函数设置为私有或删除,防止外部创建或拷贝对象。
  2. 提供一个指向单例对象的static指针,并在程序入口之前先将其初始化为空。
  3. 提供一个全局访问点获取单例对象
namespace Lazy_man {// 懒汉模式 - 第一次获取对象时,在创建对象class Singleton {public:static Singleton* GetInstance() {// 双检查if (_pinst == nullptr){//_mtx.lock();unique_lock<mutex> loc(_mtx); // 加锁 出了作用域解锁if (_pinst == nullptr) {_pinst = new Singleton;}//_mtx.unlock();}return _pinst;}static void DelInstance() {delete _pinst;_pinst = nullptr;}Singleton(const Singleton&) = delete;private:Singleton() {};static Singleton* _pinst;static mutex _mtx;};Singleton* Singleton::_pinst = nullptr;mutex Singleton::_mtx;// 1、如果要手动释放单例对象,可以调用DelInstance// 2、如果需要程序结束时,正常释放单例对象,可以加入下面的设计class GC {~GC(){Singleton::DelInstance();}};static GC gc();int x() {//Singleton s1; // 报错//Singleton copy(*Singleton::GetInstance()); // 报错cout << Singleton::GetInstance() << endl;cout << Singleton::GetInstance() << endl;vector<std::thread > vthreads;int n = 16;for (int i = 0; i < n; ++i) {vthreads.push_back(std::thread([]() {//cout << std::this_thread::get_id() << ":";cout << Singleton::GetInstance() << endl;}));}for (auto& e : vthreads){e.join();}return 0;}
}

new出来之后是否需要释放?

一般单例模式对象不需要考虑释放。单例模式的类的一个对象通常在整个程序运行期间都会使用,因此最后不delete也不会有问题,只要进程最终正常结束,对象的资源就会由OS自动释放。

懒汉模式优缺点

优点:第一次使用实例对象时,创建对象,进程启动无负载,多个单例实例启动顺序自由控制。
缺点:复杂

线程安全相关问题:

懒汉模式是存在线程安全的问题的,因为懒汉模式是在main函数之后的,意味着调用GetInstance函数获取单例对象时,可能会有多个线程同时执行新建单例对象,如果不对这个过程进行保护,此时这多个线程就会各自创建出一个单例对象,并且还会造成覆盖,导致内存泄漏,所以需要对这个过程进程加锁保护

双检查:

上面这种情况只有第一次需要加锁保护,后续因为单例对象已经存在了就无需创建单例对象,后续的加锁解锁无意义
外层新加的if判断可以避免了后续无意义的加锁解锁操作

单例对象的释放:

单例对象创建后一般在整个程序运行期间都可能会使用,所以我们可以不考虑单例对象的释放,程序正常结束时会自动将资源归还给操作系统
也可以在单例类中实现一个垃圾回收类,在垃圾回收类的析构函数中完成单例对象的释放。在单例类中定义一个静态的垃圾回收类对象,程序结束时,这个静态的垃圾回收类对象析构时,便对单例对象进行了释放

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

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

相关文章

计算机竞赛 深度学习实现行人重识别 - python opencv yolo Reid

文章目录 0 前言1 课题背景2 效果展示3 行人检测4 行人重识别5 其他工具6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习的行人重识别算法研究与实现 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c…

28 drf-Vue个人向总结-1

文章目录 前后端分离开发展示项目项补充知识开发问题浏览器解决跨域问题 drf 小tips设置资源root目录使用自定义的user表设置资源路径media数据库补充删除表中数据单页面与多页面模式过滤多层自关联后端提交的数据到底是什么jwt token登录设置普通的 token 原理使用流程解析 jw…

wallis匀色算法、直方图匹配、颜色转移方法比较

算法原理 这三种方法应该是比较基础的匀色处理算法 三个算法的原理比较简单&#xff0c;具体原理大家可以自己百度 &#xff08;1&#xff09;wallis匀色原理主要在于利用Wallis滤波器使原始图像的均值和标准差与参考影像相当&#xff0c;从而使原始影像和参考影像具有相近的色…

WebPack-打包工具

从图中我们可以看出&#xff0c;Webpack 可以将多种静态资源 js、css、less 转换成一个静态文件&#xff0c;减少了页面的请求. 下面举个例子 &#xff1a; main.js 我们只命名导出一个变量 export const name"老六"index.js import { name } from "./tset/…

第P7周—咖啡豆识别(1)

数据集及wen件目录介绍&#xff1a; 数据集&#xff1a;工作台 - Heywhale.com 一、前期工作 1.1 数据详情 import torch import torch.nn as nn import torchvision.transforms as transforms import torchvision from torchvision import transforms, datasets import os,…

聊聊KISS(Keep It Simple, Stupid)原则

文章目录 1. 前言2. KISS原则的几项描述3. KISS原则和奥卡姆剃刀原则区别 1. 前言 KISS原则&#xff0c;是Keep It Simple, Stupid的缩写&#xff0c;翻译成中文就是“保持简单&#xff0c;愚蠢的人也能懂”。这是一种鼓励简单设计的设计原则。 KISS原则的主要思想是&#x…

python+pygame+opencv+gpt实现虚拟数字人直播(有趣的探索)

AI技术突飞猛进&#xff0c;不断的改变着人们的工作和生活。数字人直播作为新兴形式&#xff0c;必将成为未来趋势&#xff0c;具有巨大的、广阔的、惊人的市场前景。它将不断融合创新技术和跨界合作&#xff0c;提供更具个性化和多样化的互动体验&#xff0c;成为未来的一种趋…

Leetcode290. 单词规律

给定一种规律 pattern 和一个字符串 s &#xff0c;判断 s 是否遵循相同的规律。 这里的 遵循 指完全匹配&#xff0c;例如&#xff0c; pattern 里的每个字母和字符串 s 中的每个非空单词之间存在着双向连接的对应规律。 解题思路&#xff1a;哈希 力扣&#xff08;LeetCode&…

数据结构:堆的简单介绍

目录 堆的介绍:(PriorityQueue) 大根堆:根节点比左右孩子节点大 小根堆:根节点比左右孩子节点小 堆的存储结构: 为什么二叉树在逻辑上用满二叉树结构,而不是普通二叉树呢? 因为如果是普通二叉树会造成资源的浪费​编辑 堆的介绍:(PriorityQueue) 堆又称优先级队列,何为优先…

3 OpenCV两张图片实现稀疏点云的生成

前文&#xff1a; 1 基于SIFT图像特征识别的匹配方法比较与实现 2 OpenCV实现的F矩阵RANSAC原理与实践 1 E矩阵 1.1 由F到E E K T ∗ F ∗ K E K^T * F * K EKT∗F∗K E 矩阵可以直接通过之前算好的 F 矩阵与相机内参 K 矩阵获得 Mat E K.t() * F * K;相机内参获得的方式…

C/C++跨平台构建工具CMake入门

文章目录 1.概述2.环境准备2.1 安装编译工具2.2 安装CMake 3.编译一个示例程序总结 1.概述 本人一直对OpenGL的3d渲染很感兴趣&#xff0c;但是苦于自己一直是Android开发&#xff0c;没有机会接触这方面的知识。就在最近吗&#xff0c;机会来了&#xff0c;以前一个做3D渲染的…

【C/C++】C/C++面试八股

C/C面试八股 C和C语言的区别简单介绍一下三大特性多态的实现原理虚函数的构成原理虚函数的调用原理虚表指针在什么地方进行初始化的&#xff1f;构造函数为什么不能是虚函数虚函数和纯虚函数的区别抽象类类对象的对象模型内存对齐是什么&#xff1f;为什么要内存对齐static关键…

微信公众号网页授权登录获取用户基本信息

概述 微信公众号网页授权登录后微信获取用户基本信息&#xff0c;部署即可运行完整demo 详细 一、前言 &#xff08;1&#xff09;适合人群 1&#xff0c;JAVA服务端开发人员 2&#xff0c;初级人员开发人员 3&#xff0c;了解spring springboot maven 3&#xff0c;了…

k8s部署gin-vue-admin框架、gitlab-ci、jenkins pipeline 、CICD

测试环境使用的jenkins 正式环境使用的gitlab-ci 测试环境 创建yaml文件 apiVersion: v1 kind: ConfigMap metadata:name: dtk-go-tiktok-admin-configlabels:app.kubernetes.io/name: dtk-go-tiktok-adminapp.kubernetes.io/business: infrastructureapp.kubernetes.io/run…

中国312个历史文化名镇及景区空间点位数据集

一部中华史&#xff0c;既是人类创造丰富物质财富的奋头史&#xff0c;又是与自然共生共存的和谐史不仅留存下悠久丰富的人文思想和情怀&#xff0c;还在各处镌刻下可流传的生活场景&#xff0c;历史文化名镇(以下简称:名镇)就是这样真实的历史画卷。“镇”是一方的政治文化中心…

Elasticsearch:使用 Elasticsearch 进行语义搜索

在数字时代&#xff0c;搜索引擎在通过浏览互联网上的大量可用信息来检索数据方面发挥着重要作用。 此方法涉及用户在搜索栏中输入特定术语或短语&#xff0c;期望搜索引擎返回与这些确切关键字匹配的结果。 虽然关键字搜索对于简化信息检索非常有价值&#xff0c;但它也有其局…

红黑树是如何实现的?

文章目录 一、红黑树的概念二、红黑树的性质三、红黑树和AVL树对比四、红黑树的插入1. 红黑树的结点定义2. 父亲的颜色3. 叔叔的颜色为红色4. 叔叔不存在5. 叔叔存在且为黑6. 插入的抽象图 五、红黑树的验证1. 检查平衡2. 计算高度与旋转次数3. 验证 六、 红黑树与AVL树的比较 …

【数据结构】——顺序表详解

大家好&#xff01;当我们学习了动态内存管理后&#xff0c;就可以写一个管理数据的顺序表了&#xff01;&#xff01;&#xff01; 顺序表的理解&#xff1a; 线性表是最基本、最简单、也是最常用的一种数据结构。线性表&#xff08;linear list&#xff09;是数据结构的一种…

青藏高原1-km分辨率生态环境质量变化数据集(2000-2020)

青藏高原平均海拔4000米以上&#xff0c;人口1300万&#xff0c;是亚洲九大河流的源头&#xff0c;为超过15亿人口提供淡水、食物和其他生态系统服务&#xff0c;被誉为地球第三极和亚洲水塔。然而&#xff0c;在该地区的人与自然的关系的研究是有限的&#xff0c;尤其是在精细…

高德地图根据两点的经纬度计算两点之间的距离(修正版)

SQL语句可以用来计算两个经纬度之间的距离。下面是一个示例的SQL语句&#xff1a; SELECT id, ( 6371 * ACOS( COS( RADIANS( lat1 ) ) * COS( RADIANS( lat2 ) ) * COS( RADIANS( lng2 ) - RADIANS( lng1 ) ) SIN( RADIANS( lat1 ) ) * SIN( RADIANS( lat2 ) ) ) ) AS dista…