智能指针(3)
目录
可能问题五:
问题分析:
答案格式:
部分1:引用计数的设计(分考点1)
代码实现:
部分2:作为类所必须的部分(分考点2)
代码实现:
部分3:拷贝构造函数(分考点3)
代码实现1:
代码实现2:
部分4:模拟指针的行为(分考点4)
代码实现:
部分5:其他重要的成员函数(分考点5)
get()
use_count()
部分6:定制删除器(分考点6)
代码实现1:
代码实现2:
代码测试:
我们接着进行智能指针的学习和试题研究。
可能问题五:
模拟实现一下weak_ptr或者unique_ptr或者shared_ptr
问题分析:
这个问题首先就明明是三个问题,因为面试时间比较短不可能连续模拟实现三个智能指针的。所以依照重要性原则,一般会重点实现shared_ptr,由于全部实现起来比较多,所以一般面试为了控制时间也会让你实现某个部分,unique_ptr和weak_ptr也会讲的。有的同学可能会认为不会让你自我实现,只需要懂得用就可以了,但是如下图越是大厂的面试题,可能或者一定是自我实现的部分(画蓝框的)的题还是相当多的!!!
答案格式:
这个问题放在了最后面说明是最难的,确实呀实践类型的题目都挺难的,但是只要知道逻辑和库里面的实现原理再加上面试的时候不紧张其实也没有那么难的。那么我们就先从最重要的shared_ptr讲起。
shared_ptr的模拟实现
我们先看看库里面是怎么写的,做为一个类有什么成员函数和成员。
用红色框框括起来的部分就是比较重要的成员函数,也就是我们等下要实现的函数,其实我们也不需要和库里面写的完全一样,主打一个差不多就可以了,大致逻辑对就行了,因为库里面在实现shared_ptr的时候还要考虑和别的智能指针兼容的问题,我们就不用考虑这么多了,我们主打一个能通过面试官的考核就行。
我们发现shared_ptr是由很多的不同部分组成的,所以我们就分部分进行解答,正好分部分也是考点。
部分1:引用计数的设计(分考点1)
引用计数_count是设计在类里面的成员变量,但是作为一个类里面的成员变量可以有多种设计方式,可以是静态成员变量,普通的成员变量或者new开辟的在堆里面的动态成员变量,到底选择哪一种呢,我们可以做如下分析:
画图表示更直观:
使用普通的成员变量:
使用静态成员变量:
我们会发现如果使用的是普通的开辟在栈里面的成员变量或者静态的全局变量都是跟着智能指针走的,但是我们的引用计数计数的是一个空间被多少个智能指针管理着,所以这个计数是肯定要跟着被管理的空间走的,以上两种表示方法在本质上就理解错了,追寻一个空间对应一个引用计数可知这个引用计数得另开辟一个空间管理,并且一个空间才开辟一个,也就是说只要遇到需要管理新空间时才新开辟智能指针。
那既然要新开辟空间就意味着,这个引用计数本质上就是一个指针,指向一个带开辟的空间。
代码实现:
int* _count;
部分2:作为类所必须的部分(分考点2)
这里主要是实现构造函数和析构函数
如果需要调用构造函数说明遇到了一个新的空间需要管理,这时也需要对引用计数进行开辟空间
由于需要管理资源管理的同时履行帮忙销毁的任务,所以需要将指向的空间连同开辟的引用计数一起销毁了,因为当一个资源需要销毁时其引用计数一定为0了。
代码实现:
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
,_count(new int(1))
{}
void release()
{
if (--(*_count) == 0)
{
delete _ptr;
delete _count;
_ptr = nullptr;
_count = nullptr;
}
}
~shared_ptr()
{
release();
}
为什么析构函数要另起一个函数,这个问题之后会解答,构造函数这么写其实还是和库里有所不同的,因为库里认为如果智能指针管理一个空的空间那引用计数为0,我们这边并没有做这种情况的另加考虑,而是笼统的都初始化为1了,但是不影响我们的大逻辑是对的。
部分3:拷贝构造函数(分考点3)
shared_ptr是支持拷贝的,拷贝分为两种一种是管理别人正在管理的空间,相当于构造。如下:
代码实现1:
shared_ptr(const shared_ptr<T>& sp)//不需要传成员对象因为成员里面本来就有
:_ptr(sp._ptr)
, _count(sp._count)//多个智能指针共同使用一个引用计数
{
++(*_count);//那个空间的相当于多了一个智能指针在管理了,所以跟着的引用计数要加1
}
这个还看不懂的自己反思一下!!!
还有一种就是将当前管理的空间和别的空间进行互换,相当于换一块空间管理不再管理当前空间了
代码如下:
代码实现2:
shared_ptr<T>& operator=(shared_ptr<T>& sp)
{
if (_ptr != sp._ptr)//不可以自己析构自己,因为没有意义
{
//~shared_ptr();//虽然可以但是不支持直接调用,这就是为什么析构函数这么写的原因了
release();//在转换指向对象时,需要先释放当前的指向对象,也就是相当于要现处理当前对象的引用计数
_ptr = sp._ptr;
_count = sp._count;
++(*_count);
}
return *this;
}
部分4:模拟指针的行为(分考点4)
这个之前都讲过了直接看代码实现吧。
代码实现:
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
部分5:其他重要的成员函数(分考点5)
由于库里面实现的函数有很多,但是重要的比较核心的就那么几个:
get()
这个函数的作用是得到并返回指向管理这个空间的智能指针及指向这个空间的指针,这个函数有大用的我们在weak_ptr的实现里面会讲。
T* get() const//返回指向一个已被管理的空间的指针
{
return _ptr;
}
use_count()
这个函数的作用是返回一个空间被多少个智能指针所管理,也就是返回智能指针指向的引用计数的大小。
int use_count() const
{
return *_count;//返回指向空间的引用计数的个数
}
部分6:定制删除器(分考点6)
定制删除器是shared_ptr自我实现里面比较难的部分了,如果面试官没问就不需要在模拟实现的时候直接体现出来,还有一个原因就是写了很容易错,其实部分1到部分5已经足以体现智能指针管理资源和模拟指针的行为的功能了,这个仅作为加分项。
首先定制删除器肯定要设计成类模板参数进行传递的成员变量,这样便于析构函数调用,因为外面知道定制删除器的类型有点多,且当其为lambda时类型未知,主要是uuid不知道,所以这么多的类型设计成模板参数来能够表达并兼容各自类型很有必要。所以构造函数很好写的如下:
代码实现1:
template<class D>
shared_ptr(T* ptr, D del)//缺省值要从右边往左边给,所以ptr不能给缺省值
:_ptr(ptr)
,_count(new int(1))
,_del(del)//如果没有传定制删除器就会默认使用缺省值进行构造,构造函数是这样的
{}
其实就是多加一个模板参数而已,看不懂了自己反思一下!!!
好啦你既然加了一个模板参数,且这个删除器del是设计成员变量,那对于这么多个类型难道在成员对象那里也加入模板参数,这个方法其实是可行但是,这是unique_ptr的设计理念,shared_ptr不支持将删除器弄成模板的样子就是不支持再传一个模板参数,那怎么办,要同时能处理这么多类型又要不使用模板参数,这个问题其实可以简化成如果可以用一个东西同时封装多个类型的变量就可以解决这个问题了。我们之前C++11学过的包装器function就好像有这个功能吧,对这里定义删除器变量就用的包装器进行封装的!!!
代码实现2:
T* _ptr;//智能指针的内部相当于指针,管理空间的
int* _count;//引用计数
function<void(T* _ptr)> _del = [](T* _ptr) {delete _ptr; };
//由于删除器的类型很多并且库里面不支持再传一个模板参数
//所以只能使用包装器对象,因为这样可以兼容很多类型
//给一个lambda样式的缺省值是因为有时候没有传删除器,无法直接使用现成的初始化
有了定制删除器对管理空间进行析构的时候就直接调用定制删除器(function对象)就可以了,不需要使用delete毕竟不是所有的类型都可以用delete进行销毁的。代码如下:
void release()
{
if (--(*_count) == 0)
{
//delete _ptr;
_del(_ptr);
delete _count;
_ptr = nullptr;
_count = nullptr;
}
}
那这样将部分1到部分6全部整合在一起就是完整的shared_ptr了。整体代码如下
shared_ptr自己模拟实现的最终答案展示:
namespace bit
{
template<class T>
class shared_ptr
{
public:
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
,_count(new int(1))
{}
template<class D>
shared_ptr(T* ptr, D del)
:_ptr(ptr)
,_count(new int(1))
,_del(del)
{}
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr)
, _count(sp._count)
{
++(*_count);
}
void release()
{
if (--(*_count) == 0)
{
_del(_ptr);
delete _count;
_ptr = nullptr;
_count = nullptr;
}
}
shared_ptr<T>& operator=(shared_ptr<T>& sp)
{
if (_ptr != sp._ptr)
{
release();
_ptr = sp._ptr;
_count = sp._count;
++(*_count);
}
return *this;
}
T* get() const
{
return _ptr;
}
int use_count() const
{
return *_count;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
~shared_ptr()
{
release();
}
private:
T* _ptr;
int* _count;
function<void(T* _ptr)> _del = [](T* _ptr) {delete _ptr; };
};
代码测试:
int main()
{
bit::shared_ptr<Date> sp1(new Date);
bit::shared_ptr<Date> sp2(sp1);
bit::shared_ptr<Date> sp3(new Date);
// 自己给自己赋值
sp3 = sp3;
sp1 = sp2;
sp1 = sp3;
sp2 = sp3;
bit::shared_ptr<FILE> sp5(fopen("test.cpp", "w"), Fclose());
bit::shared_ptr<int> sp6((int*)malloc(40), [](int* ptr)
{
cout << "free:" << ptr << endl;
free(ptr);
});
return 0;
}
经过测试发现我们写的实现逻辑没有什么问题!!!
其实你到面试的时候,一问到模拟实现shared_ptr就最先想到是一个名为shared_ptr的类,然后依照上面的部分1到5逐步回忆着其中的逻辑然后按顺序写出来,我相信你一定可以完成的。
相关文章:

智能指针(3)
目录 可能问题五: 问题分析: 答案格式: shared_ptr的模拟实现 部分1:引用计数的设计(分考点1) 代码实现: 部分2:作为类所必须的部分(分考点2) 代码实现: 部分3:拷贝构造函数…...

spring源码拓展点3之addBeanPostProcesser
概述 在refresh方法中的prepareBeanFactory方法中,有一个拓展点:addBeanPostProcessor。即通过注入Aware对象从而将容器中的某些值设置到某个bean中。 beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));aware接口调用 …...

【计网】理解TCP全连接队列与tcpdump抓包
希望是火,失望是烟, 生活就是一边点火,一边冒烟。 理解TCP全连接队列与tcpdump抓包 1 TCP 全连接队列1.1 重谈listen函数1.2 初步理解全连接队列1.3 深入理解全连接队列 2 tcpdump抓包 1 TCP 全连接队列 1.1 重谈listen函数 这里我们使用…...

react18中实现简易增删改查useReducer搭配useContext的高级用法
useReducer和useContext前面有单独介绍过,上手不难,现在我们把这两个api结合起来使用,该怎么用?还是结合之前的简易增删改查的demo,熟悉vue的应该可以看出,useReducer类似于vuex,useContext类似…...

排序算法 —— 冒泡排序
目录 1.冒泡排序的思想 2.冒泡排序的实现 3.冒泡排序的总结 1.冒泡排序的思想 冒泡排序的思想就是在待排序序列中依次比较相邻两个元素,将大的or小的元素往后挪,每一趟都能保证将至少一个元素挪动到正确的位置,然后在待排序序列中重复该过…...

QT--文本框 QLineEdit、qtextedit
在Qt中,文本框(QLineEdit 或 QTextEdit)和标签(QLabel)是两种不同的部件(widget),它们的主要区别在于用途和功能: QLabel(标签) 用途࿱…...

Qt编写的modbus模拟器/支持网络和串口以及websocket/支持网络rtu
一、使用说明 1.1 设备模拟-Com 第一步,填写要模拟的设备地址,0表示自动处理,也就是收到什么地址就应答什么地址。第二步,填写对应的串口号和波特率。第三步,单击打开串口,成功后会变成关闭串口字样。单击…...

Standard_Matrix
文章目录 假设我们有一个样本矩阵X,每一列表示一个样本,现在我们要把样本转换成均值为0,方差为1的样本矩阵 X s t a n d a r d X − μ s \begin{equation} X_{standard}\frac{X-\mu}{s} \end{equation} XstandardsX−μpython 测试…...

js 通过input,怎么把选择的txt文件转为base64格式
文章目录 基本概念与作用说明Base64编码File对象相互转换的意义 从File对象到Base64编码从Base64编码到File对象批量转换File对象为Base64编码批量转换Base64编码为File对象功能使用思路思路一:动态生成预览思路二:异步处理与用户反馈思路三:…...

华为HCIP-openEuler认证详解
华为HCIP认证(Huawei Certified ICT Professional)是华为提供的专业级ICT技术认证,它旨在验证技术人员在特定技术领域的专业知识和实践能力。对于华为欧拉(openEuler)方向的HCIP认证,即HCIP-openEuler&…...

YOLO11改进 | 注意力机制 | 添加双重注意力机制 DoubleAttention【附代码+小白必备】
秋招面试专栏推荐 :深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 💡💡💡本专栏所有程序均经过测试,可成功执行💡💡💡 在本文中,给大家带来的教程是…...

sentinel原理源码分析系列(四)-ContextEntry
启动和初始化完成后,调用者调用受保护资源,触发sentinel的机制,首先构建或获取Context和获取Entry,然后进入插槽链,决定调用是否通过,怎样通过 上图展示构建Context和获取Entry的类互动图 获取或构建Conte…...

Tcp协议讲解与守护进程
TCP协议:面向链接,面向字节流,可靠通信 创建tcp_server 1.创建套接字 域:依旧选择AF_INET 连接方式: 选择SOCK_STREAM 可靠的 2.bind 3.监听装置 client要通信,要先建立连接࿰…...

学习threejs,THREE.LineDashedMaterial 虚线材质,基于gosper高斯帕曲线生成雪花动画
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️THREE.LineDashedMaterial虚…...

LeetCode 热题100之哈希
1.两数之和 思路分析1(暴力法) 双重循环枚举满足num[i] nums[j] target的索引,刚开始不知道如何返回一对索引。后来知道可以直接通过return {i,j}返回索引;注意:j应该从i1处开始,避免使用两次相同的元素…...

软工——模块设计(爱啦爱啦)
过程设计的工具 一、程序流程图 程序流程图又称为程序框图,它是历史最悠久、使用最广泛的描述过程设计的方法。 它的主要优点是对控制流程的描绘很直观,便于初学者掌握。 程序流程图历史悠久,至今仍在广泛使用着。 程序流程图的主要缺点&a…...

Xmind一款极简思维导图和头脑风暴软件,支持PC和移动端,Xmind 2024.10.01101版本如何升级到Pro版?简单操作,最新可用!
文章目录 Xmind下载安装Xmind免费升级到Pro Xmind 是一款全功能的思维导图和头脑风暴软件,不限制节点和文件数,创新无限,界面纯净简洁无广告,支持PC和移动端,思维导图和大纲视图自由切换,可本地化文档存储&…...

自动化工具:Ansible
目录 一、运维自动化工具有哪些 二、Ansible 概述 1、Ansible 概念 2、Ansible 特点 3、Ansible 工作流程 三、安装部署Ansible 1、环境部署 2、管理节点安装 Ansible 3、查看Ansible相关文件 4、配置主机清单 5、免密管理 ssh-keygen 5.1、测试连通性 5.2、简洁输…...

我是类(最终版)
文章目录 再看构造函数类型转换static静态成员友元内部类匿名对象对象拷贝时的编译器优化 再看构造函数 本标题的目的是解决如下问题:当实现MyQueue时,我们不需要写默认构造函数,因为编译器会调用Stack的默认构造,但是࿰…...

详解ip route
ip route命令用于查看 Linux 系统中的路由表信息。 路由表包含的主要信息 目标网络地址(Destination) 显示网络的目标地址,可以是一个具体的网络地址(如192.168.1.0/24),也可以是一个默认网络(…...

OpenGL进阶系列04 - OpenGL 点精灵
一:概述 OpenGL 点精灵是一种渲染技术,用于在3D场景中渲染小的、可缩放的点。它们通常用于表示粒子效果、光源或其他小物体。点精灵会根据视图和投影矩阵自动调整大小,使其始终在屏幕上保持一致的视觉效果。实现时,点精灵通常通过使用纹理和适当的着色器来增加视觉效果。 …...

VSCode按ctrl与鼠标左键无法实现跳转的解决办法
vscode编译环境老是出问题,下面介绍两种解决方法 需要提前配置好代码编译需要的库以及编译器位置等等。 ctrlshiftp,输入 >C/C配置(JSON) 打开生成的c_cpp_properties.json {"configurations": [{"name": "Li…...

U盘数据丢失不用慌,这4个工具可以帮你恢复。
因为将大量的数据存到U盘里面很方便,所以U盘使用也很广泛。但是里面的数据丢失想必很多朋友都碰到过,不过现在有很多方法都可以帮助大家将数据回顾回来。这里我便筛选了几款比较好的数据恢复工具,在这里跟大家分享。 1、福昕U盘恢复软件 直通…...

如何在Ubuntu上挂载一块硬盘:详解方案与实操步骤【小白无坑版】
如何在Ubuntu上挂载一块硬盘:详解方案与实操步骤 一、引言 在日常的开发或使用中,我们经常会遇到这样的场景:系统硬盘空间不足,需要额外挂载一块硬盘以扩展存储;或者我们需要将一块新硬盘用于专门存储数据或备份项目…...

【JAVA】第三张_Eclipse下载、安装、汉化
简介 Eclipse是一种流行的集成开发环境(IDE),可用于开发各种编程语言,包括Java、C、Python等。它最初由IBM公司开发,后来被Eclipse Foundation接手并成为一个开源项目。 Eclipse提供了一个功能强大的开发平台&#x…...

go-zero系列-限流(并发控制)及hey压测
参考地址: go-zero系列-限流(并发控制):https://go-zero.dev/docs/tutorials/service/governance/limiter hey地址:https://github.com/rakyll/hey1、压测工具hey下载安装: 会安装到GOPATH/bin目录下 go install github.com/ra…...

Electron-(三)网页报错处理与请求监听
在前端开发中,Electron 是一个强大的框架,它允许我们使用 Web 技术构建跨平台的桌面应用程序。在开发过程中,及时处理网页报错和监听请求是非常重要的环节。本文将详细介绍 Electron 中网页报错的日志记录、webContents 的监听事件以及如何监…...

银河麒麟(debian)下安装postgresql、postgis
1、安装postgresql、postgis sudo apt update sudo apt install postgresql postgresql-contrib sudo apt install postgis postgresql-12-postgis-32、创建一个使用postgis的数据库 sudo -i -u postgres #postgres管理员用户createdb gisdb #创建新的gisdb数据库 psql -d gi…...

【已解决】【Hadoop】 Shell命令易错点及解决方法
Hadoop是一个强大的分布式系统,用于处理大规模数据集。在使用Hadoop的过程中,熟练掌握其Shell命令是必不可少的。本文将介绍几个常用的Hadoop Shell命令,并总结一些常见的操作错误及其解决方法。 Hadoop Shell命令简介 Hadoop提供了多种She…...

ST7789读取ID错误新思路(以STC32G为例)
1.前言 前两天刚把ST7789写入搞定,这两天想折腾一下读取。最开始是读ID,先是用厂家送的程序,程序里面用的是模拟I8080协议,一切正常。后来我用STC32G的内置LCM模块,发现读取不出来。更神奇的是ID读不出来,…...