C++—特殊类设计设计模式
目录
- C++—特殊类设计&设计模式
- 1.设计模式
- 2.特殊类设计
- 2.1设计一个无法被拷贝的类
- 2.2设计一个只能在堆上创建对象的类
- 2.3设计一个只能在栈上创建对象的类
- 2.4设计一个类,无法被继承
- 2.5设计一个类。这个类只能创建一个对象【单例模式】
- 2.5.1懒汉模式实现
- 2.5.2饿汉模式实现
- 2.5.3懒汉的饿汉的区别
C++—特殊类设计&设计模式
1.设计模式
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似
之前已经接触过一些设计模式了
比如:
- 迭代器模式
- 适配器模式
还有一些设计模式——工厂模式,装饰器模式,观察者模式,单例模式、
2.特殊类设计
下面是一些常见的特殊类设计
2.1设计一个无法被拷贝的类
拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
这个前面在异常的时候其实就说过了,两种方式,分别是c++98和c++11提供的
- c++11的方法:
C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。
class NotCopy
{// 其实就是将拷贝构造和赋值运算符重载给禁掉就行了,之前有讲过// 在c++98就是将之声明不定义,然后将两个成员函数弄成私有的,这样外面就无法使用者两个函数了// 在c++11可以使用delete关键字
public:NotCopy(const NotCopy& n) = delete;NotCopy& operator=(const NotCopy& n) = delete;
private:int _a;
};
- c++98的方法:
class NotCopy
{// 在c++98就是将之声明不定义,然后将两个成员函数弄成私有的,这样外面就无法使用者两个函数了// 在c++11可以使用delete关键字
public:private:NotCopy(const NotCopy& n);NotCopy& operator=(const NotCopy& n);int _a;
};
- 原因:
- 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不
能禁止拷贝了
- 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
2.2设计一个只能在堆上创建对象的类
实现方式:
-
将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
-
提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
//2.设计一个只能在堆上创建对象的类
class OnlyHead
{
public:static OnlyHead* GetObj(){return new OnlyHead;// new OnlyHead和new OnlyHead()的区别// 就是第一个是隐式调用默认构造函数// 第二个是显式调用默认构造函数}// 除了构造函数不能给外面直接调用,拷贝构造和赋值运算符重载也要禁掉OnlyHead(const OnlyHead& o) = delete;OnlyHead& operator=(const OnlyHead& o) = delete;private:// 在栈上定义的对象一定会调用默认构造函数,所以直接弄成私有的OnlyHead(){}};int main()
{//OnlyHead hp;//OnlyHead* p = new OnlyHead;OnlyHead* p = OnlyHead::GetObj(); // 如果不释放p就会内存泄漏shared_ptr<OnlyHead> sp(OnlyHead::GetObj()); //交给智能指针//OnlyHead copy(*sp); // 拷贝构造要禁掉return 0;
}
2.3设计一个只能在栈上创建对象的类
可以用2.2的思路,先将构造私有,然后直接给一个类内静态方法
也可以将new直接重载,直接禁掉
// 3.设计一个只能在栈上创建对象的类
class OnlyStack
{
public:// 第一种思路: 直接不给new,重载new【这个思路无法阻止静态区的对象的创建】void* operator new(size_t size) = delete;// 第二种思路:将构造函数设置为私有的,然后提供一个static方法给外部调用// 这个思路不能禁掉拷贝构造,因为getobj返回值这里会有一次拷贝构造//【这个思路无法阻止静态区的对象的创建】static OnlyStack getobj(){return OnlyStack(); //类里面可以调用私有的构造函数}
private:OnlyStack(){}
};int main()
{OnlyStack* p = new OnlyStack;OnlyStack* p = new OnlyStack();OnlyStack o = OnlyStack::getobj(); //创建栈上的对象,注意这个思路不能禁掉拷贝构造// 这里实际上调用了拷贝构造static OnlyStack os = OnlyStack::getobj();return 0;
}
2.4设计一个类,无法被继承
这个简单,就不仔细说了
// 4.设计一个类,无法被继承
class NoInherit final
{// 两个思路,c++98:直接让构造函数私有化,因为子类的构造需要调用父类的构造// c++11:final关键字
};
2.5设计一个类。这个类只能创建一个对象【单例模式】
2.5.1懒汉模式实现
单例模式:一个类在全局(进程)中只能有一个实例对象
// 5.设计一个类。这个类只能创建一个对象【单例模式】
class Singleton
{
public:static Singleton* GetInstance(){if (_p == nullptr){_p = new Singleton;}return _p;}// 在这个情况下,拷贝构造也要禁掉Singleton(const Singleton& s) = delete;private:Singleton(){}// 需要再创建一个变量,来控制对象只能存在一个// 其实就是弄一个所有对象共用的变量,只要该变量标记了,就说明已经有一个对象了,那就不能再调用构造函数了static Singleton* _p;
};Singleton* Singleton::_p = nullptr; //初始化int main()
{Singleton* s1 = Singleton::GetInstance();Singleton* s2 = Singleton::GetInstance();Singleton* s3 = Singleton::GetInstance();// 这里三个获取的都是同一个对象cout << s1 << endl;cout << s2 << endl;cout << s3 << endl;//Singleton s4(Singleton::GetInstance());//拷贝构造要禁掉return 0;
}
但是上面这个代码是有问题的,这个单例模式会有线程安全问题,因为当多线程操作的时候,这里有一个公共资源,即临界资源——_p,因此就会出现线程安全问题。可能会创建出多个对象。这样单例模式就被破坏了
因此对临界资源_p加锁即可
#include<mutex>
class Singleton
{
public:static Singleton* GetInstance(){_mtx.lock();if (_p == nullptr){_p = new Singleton;}_mtx.unlock();return _p;}// 在这个情况下,拷贝构造也要禁掉Singleton(const Singleton& s) = delete;private:Singleton(){}// 需要再创建一个变量,来控制对象只能存在一个// 其实就是弄一个所有对象共用的变量,只要该变量标记了,就说明已经有一个对象了,那就不能再调用构造函数了static Singleton* _p; // _p是一个临界资源,要保护它static mutex _mtx; //为了线程安全,加一个锁
};Singleton* Singleton::_p = nullptr; //初始化
mutex Singleton::_mtx; //定义, 类里面的只是声明
这个代码就不会出现线程安全问题。
但是这个代码还是有点问题,因为虽然对临界区加锁了,但是临界区内有可能发生异常,new可能会抛异常,一旦抛异常就会造成死锁,因为这里应该用RAII思想来解决
#include<mutex>
class Singleton
{
public:static Singleton* GetInstance(){//_mtx.lock();{ //这个花括号是为了控制unique_lock的生命周期,不影响临界区后面的代码unique_lock<mutex> lock(_mtx);if (_p == nullptr){_p = new Singleton;}}//_mtx.unlock();return _p;}// 在这个情况下,拷贝构造也要禁掉Singleton(const Singleton& s) = delete;private:Singleton(){}// 需要再创建一个变量,来控制对象只能存在一个// 其实就是弄一个所有对象共用的变量,只要该变量标记了,就说明已经有一个对象了,那就不能再调用构造函数了static Singleton* _p; // _p是一个临界资源,要保护它static mutex _mtx; //为了线程安全,加一个锁
};Singleton* Singleton::_p = nullptr; //初始化
mutex Singleton::_mtx; //定义, 类里面的只是声明
上述代码仍然有一些缺陷,可以继续优化。【即,我们只需要对第一次访问临界资源_p进行加锁保护即可,因为后面不会再有修改临界资源的情况出现】
这里做一个经典的双检查
static Singleton* GetInstance()
{//_mtx.lock();if(_p == nullptr) //这里是一个双检查{ unique_lock<mutex> lock(_mtx);if (_p == nullptr){_p = new Singleton;}} //并且出了这里,unique_lock<mutex>的生命周期就结束了//_mtx.unlock();return _p;
}
下面是完整的代码:【这里是一个懒汉模式的单例模式】
#include<mutex>
class Singleton
{
public:static Singleton* GetInstance(){//_mtx.lock();if(_p == nullptr) //这里是一个双检查{ unique_lock<mutex> lock(_mtx);if (_p == nullptr){_p = new Singleton;}} //并且出了这里,unique_lock<mutex>的生命周期就结束了//_mtx.unlock();return _p;}// 在这个情况下,拷贝构造也要禁掉Singleton(const Singleton& s) = delete;// 释放资源static void DelInstance(){unique_lock<mutex> lock(_mtx);delete _p;_p = nullptr;}private:Singleton(){}// 需要再创建一个变量,来控制对象只能存在一个// 其实就是弄一个所有对象共用的变量,只要该变量标记了,就说明已经有一个对象了,那就不能再调用构造函数了static Singleton* _p; // _p是一个临界资源,要保护它static mutex _mtx; //为了线程安全,加一个锁
};Singleton* Singleton::_p = nullptr; //初始化
mutex Singleton::_mtx; //定义, 类里面的只是声明
如果想程序在结束之后,自动释放单例对象,可以引入尝试下面这个代码的思路
// 释放资源static void DelInstance(){// 这种思路下,不需要加锁了。因为_mtx锁可能已经释放了// 并且main函数都结束了,主线程结束了,其他线程肯定都结束了,不存在多线程的场景了delete _p;_p = nullptr;}// 如果手动释放单例对象,可以手动调用DelInstance()
// 如果想在程序结束之后自动释放单例对象,可以引入一个小机制,利用生命周期
class GC
{
public:~GC(){Singleton::DelInstance();}
};// 这里定义一个gc的静态变量,在当前程序结束之后,就会调用DelInstance()来释放单例对象
static GC gc;
2.5.2饿汉模式实现
class Singleton{public:static Singleton* GetInstance(){return &_inst;}Singleton(const Singleton& s) = delete;private:Singleton(){}static Singleton _inst;};Singleton Singleton::_inst; //在main函数之前就在静态区把唯一的对象创建好了, 不存在线程安全的问题了
2.5.3懒汉的饿汉的区别
-
懒汉模式需要考虑线程安全和内存泄漏的问题,实现相对更复杂。饿汉就不需要考虑这些问题,实现起来也相对简单、
-
懒汉模式是在需要的时候才创建对象并初始化,相对来说不会影响程序的进行,而饿汉模式是一开始就创建对象并初始化,如果代码量大,就会导致程序运行缓慢,影响用户体验
-
饿汉模式无法保证对象的创建顺序,比如有多个单例类,并且有依赖关系(B依赖A), 要求先创建A,在创建B,那就不能用饿汉模式,要用懒汉
-
如果在构造函数中有动态库链接,和创建线程(要链接线程库),那么就不能用饿汉模式。因为饿汉在main函数之前就初始化了,这个时候不知道库是否链接了,如果没有链接就会崩
相关文章:

C++—特殊类设计设计模式
目录 C—特殊类设计&设计模式1.设计模式2.特殊类设计2.1设计一个无法被拷贝的类2.2设计一个只能在堆上创建对象的类2.3设计一个只能在栈上创建对象的类2.4设计一个类,无法被继承2.5设计一个类。这个类只能创建一个对象【单例模式】2.5.1懒汉模式实现2.5.2饿汉模…...

Android 手写签名功能详解:从原理到实践
Android 手写签名功能详解 1. 引言2. 手写签名核心实现:SignatureView 类3. 交互层实现:MainActivity 类4. 布局与配置5. 性能优化与扩展方向 1. 引言 在电子政务、金融服务等移动应用场景中,手写签名功能已成为提升用户体验与业务合规性的关…...

Level2.8蛇与海龟(游戏)
#小龟快跑游戏 输入难度(1-5),蛇追到龟,游戏结束 #分析问题:从局部>整体 #游戏画面:创建画笔(海龟蛇)>1.海龟移动(键盘控制)>2.蛇(自动追踪,海龟位置)>3.海龟(限定范围,防止跑出画布之外)>4.游戏&…...

【Android构建系统】如何在Camera Hal的Android.bp中选择性引用某个模块
背景描述 本篇文章是一个Android.bp中选择性引用某个模块的实例。 如果是Android.mk编译时期,在编译阶段通过某个条件判断是不是引用某个模块A, 是比较好实现的。Android15使用Android.bp构建后,要想在Android.bp中通过自定义的一个变量或者条件实现选…...

【Canvas与诗词】醉里挑灯看剑 梦回吹角连营
【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>醉里挑灯看剑梦回吹角连营 Draft1</title><style type"…...
Hue面试内容整理-Hue 架构与前后端通信
Cloudera Hue 是一个基于 Web 的 SQL 助手,旨在为数据分析师和工程师提供统一的界面,以便与 Hadoop 生态系统中的各个组件(如 Hive、Impala、HDFS 等)进行交互。其架构设计强调前后端的分离与高效通信,确保系统的可扩展性和可维护性。以下是 Hue 架构及其前后端通信机制的…...
Linux搜索
假如我们要搜索 struct sockaddr_in 我们在命令终端输入 cd/usr/include/ //进入头文件目录地址 /usr/include/ grep " struct sockaddr_in { " *-nir (*是在当前目录,n 是找出来显示行数…...
Git基础原理和使用
Git 初识 一、版本管理痛点 在日常工作和学习中,我们经常遇到以下问题: - 通过不断复制文件来保存历史版本(如报告-v1、报告-最终版等) - 版本数量增多后无法清晰记住每个版本的修改内容 - 项目代码管理存在同样问题 二、版本控…...

实现视频分片上传 OSS
访问 OSS 有两种方式,本文用到的是使用临时访问凭证上传到 OSS,不同语言版本的代码参考: 使用STS临时访问凭证访问OSS_对象存储(OSS)-阿里云帮助中心 1.安装并使用 首先我们要安装 OSS: npm install ali-oss --save 接着我们…...

网络I/O学习(一)
一、什么是网络IO? 就是客户端和服务端之间的进行通信的通道(fd)。 二、网络IO通信步骤 1、建立套接字 int socketfd socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in servaddr; servaddr.sin_family AF_INET; servaddr.sin_addr.s_addr htonl(INADDR_A…...
4:OpenCV—保存图像
将图像和视频保存到文件 在许多现实世界的计算机视觉应用中,需要保留图像和视频以供将来参考。最常见的持久化方法是将图像或视频保存到文件中。因此,本教程准备解释如何使用 OpenCV C将图像和视频保存到文件中。 将图像保存到文件 可以学习如何保存从…...

Selenium-Java版(css表达式)
css表达式 前言 根据 tag名、id、class 选择元素 tag名 #id .class 选择子元素和后代元素 定义 语法 根据属性选择 验证CSS Selector 组选择 按次序选择子节点 父元素的第n个子节点 父元素的倒数第n个子节点 父元素的第几个某类型的子节点 父元素的…...

产品更新丨谷云科技 iPaaS 集成平台 V7.5 版本发布
五月,谷云科技 iPaaS 集成平台保持月度更新, V7.5 版本于近日正式发布。我们一起来看看新版本有哪些升级和优化。 核心新增功能:深化API治理,释放连接价值 API网关:全链路可控,精准管控业务状态 业务状态…...

深度学习让鱼与熊掌兼得
通常,一个大的复杂的模型的loss会低,但是拟合方面不够,小的模型在拟合方面更好,但是loss高,我们可以通过深度学习来得到一个有着低loss的小模型 我们之前学过,peacewise linear可以用常数加上一堆这个阶梯型函数得到,然后因为peacewise linear可以逼近任何function,所以理论上…...

TDuckX 2.6 正式发布|API 能力开放,核心表单逻辑重构,多项实用功能上线。
大家好,TDuckX 2.6 已正式发布。 本次更新以可集成性提升、数据处理能力增强和交互体验优化为核心,新增了包括 新增OpenAPI 模块、表单数据批量修改、字段导出分列 等多个面向开发者和实际业务落地场景的功能。 我们也重构了部分底层逻辑模块ÿ…...
LeetCode Hot100刷题——除自身以外数组的乘积
238. 除自身以外数组的乘积 给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&a…...

JAVA EE(进阶)_进阶的开端
别放弃浸透泪水的昨天,晨光已为明天掀开新篇 ——陳長生. ❀主页:陳長生.-CSDN博客❀ 📕上一篇:JAVA EE_HTTP-CSDN博客 1.什么是Java EE Java EE(Java Pla…...
PDF批量合并拆分+加水印转换 编辑 加密 OCR 识别
各位办公小能手们!你们有没有遇到过被PDF文件折腾得晕头转向的时候呀?其实啊,有专门处理、编辑、管理和优化PDF文件的软件,那就是PDF工具。它功能老多了,有文档格式转换、内容编辑、页面管理、安全保护这些核心功能。下…...
Go语言交替打印问题及多种实现方法
Go语言交替打印问题及多种实现方法 在并发编程中,多个线程(或 goroutine)交替执行任务是一个经典问题。本文将以 Go 语言为例,介绍如何实现多个 goroutine 交替打印数字的功能,并展示几种不同的实现方法。 Go 语言相关…...

ArcGIS Pro调用多期历史影像
一、访问World Imagery Wayback,基本在我国范围 如下图: 二、 放大到您感兴趣的区域 三、 查看影像版本信息 点击第二步的按钮后,便可跳转至World Imagery (Wayback 2025-04-24)的相关信息。 四 、点击上图影像版本信息,页面跳转…...
10.11 LangGraph多角色Agent开发实战:生产级AI系统架构与性能优化全解析
LangGraph 项目:High-level API for Multi-actor Agents 关键词:LangGraph 多角色 Agent, 状态管理, 持久化机制, 工作流编排, 生产级 AI 系统 1. LangGraph 设计哲学与架构演进 LangGraph 是 LangChain 生态中首个面向 多角色协作 Agent 的高阶 API 框架,其核心设计思想可…...

组态王|组态王中如何添加西门子1200设备
哈喽,你好啊,我是雷工! 最近使用组态王采集设备数据,设备的控制器为西门子的1214CPU, 这里边实施边记录,以下为在组态王中添加西门子1200PLC的笔记。 1、新建 在组态王工程浏览器中选择【设备】→点击【新建】。 2、选择设备 和设备建立通讯要通过对应的设备驱动。 在…...
发布时将多个bpl 打包成一个bpl的方法,或者说:不需要vcl60.bpl情况下 18.5K的exe 照常可以运行。
其实这种方式 就是把项目的逻辑和业务 和 依赖分开。 控件和IDE 相对来说一段时间内不会改变。 更新只是更新一些项目的逻辑,例如你在代码里多写了一个 if ,这样就可以只更新这个极小的exe。 题:关于bpl发布时将vcl60.bpl,vcld…...

6.2.2邻接表法-图的存储
知识总览: 为什么要用邻接表 因为邻接矩阵的空间复杂度高(O(n)),且不适合边少的稀疏图,所以有了邻接表 用代码表示顶点、图 声明顶点图信息 声明顶点用一维数组存储各个顶点的信息,一维数组字段包括2个,每个顶点的…...

C++23 放宽范围适配器以允许仅移动类型(P2494R2)
文章目录 引言背景与动机提案内容与实现细节提案 P2494R2实现细节编译器支持 对开发者的影响提高灵活性简化代码向后兼容性 示例代码总结 引言 C23 标准中引入了许多重要的改进,其中一项值得关注的特性是放宽范围适配器(range adaptors)以允…...

【技海登峰】Kafka漫谈系列(十一)SpringBoot整合Kafka之消费者Consumer
【技海登峰】Kafka漫谈系列(十一)SpringBoot整合Kafka之消费者Consumer spring-kafka官方文档: https://docs.spring.io/spring-kafka/docs/2.8.10/reference/pdf/spring-kafka-reference.pdf KafkaTemplate API: https://docs.spring.io/spring-kafka/api/org/springframe…...
Spring Boot三层架构设计模式
Spring Boot 的三层架构设计模式是一种经典的软件分层设计模式,旨在将应用程序划分为 表现层(Controller)、业务逻辑层(Service)、数据访问层(Repository/DAO),通过清晰的职责划分提…...
在Java中调用Ant命令
在Java中调用Ant命令 在Java程序中调用Ant命令有几种方法,下面介绍两种常用的方式: 1. 使用Runtime.exec()方法 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader;public class AntRunner {public stat…...

WebRTC技术下的EasyRTC音视频实时通话SDK,助力车载通信打造安全高效的智能出行体验
一、方案背景 随着智能交通与车联网技术的飞速发展,车载通信在提升行车安全、优化驾驶体验以及实现智能交通管理等方面发挥着越来越重要的作用。传统的车载通信方式在实时性、稳定性以及多媒体交互能力上存在一定局限,难以满足现代车载场景日益复杂的…...

数据科学和机器学习的“看家兵器”——pandas模块 之二
目录 pandas 模块介绍 4.2 pandas 数据读取 4.2.1 课程目标 4.2.2 读取 Excel 文件中的数据 (一)读取某个工作表中的数据 (二)读取指定数据列的标签内容 (三)读取指定数据行的标签内容 (四)读取指定行或者列 4.2.3、读取 CSV 文件数据 4.2.4、课程总结回顾 4.2.5、课后…...