智能指针(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),也可以是一个默认网络(…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...

Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...

2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...
django blank 与 null的区别
1.blank blank控制表单验证时是否允许字段为空 2.null null控制数据库层面是否为空 但是,要注意以下几点: Django的表单验证与null无关:null参数控制的是数据库层面字段是否可以为NULL,而blank参数控制的是Django表单验证时字…...
嵌入式常见 CPU 架构
架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集,单周期执行;低功耗、CIP 独立外设;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel(原始…...
MFE(微前端) Module Federation:Webpack.config.js文件中每个属性的含义解释
以Module Federation 插件详为例,Webpack.config.js它可能的配置和含义如下: 前言 Module Federation 的Webpack.config.js核心配置包括: name filename(定义应用标识) remotes(引用远程模块࿰…...
0x-3-Oracle 23 ai-sqlcl 25.1 集成安装-配置和优化
是不是受够了安装了oracle database之后sqlplus的简陋,无法删除无法上下翻页的苦恼。 可以安装readline和rlwrap插件的话,配置.bahs_profile后也能解决上下翻页这些,但是很多生产环境无法安装rpm包。 oracle提供了sqlcl免费许可,…...
Python实现简单音频数据压缩与解压算法
Python实现简单音频数据压缩与解压算法 引言 在音频数据处理中,压缩算法是降低存储成本和传输效率的关键技术。Python作为一门灵活且功能强大的编程语言,提供了丰富的库和工具来实现音频数据的压缩与解压。本文将通过一个简单的音频数据压缩与解压算法…...

AxureRP-Pro-Beta-Setup_114413.exe (6.0.0.2887)
Name:3ddown Serial:FiCGEezgdGoYILo8U/2MFyCWj0jZoJc/sziRRj2/ENvtEq7w1RH97k5MWctqVHA 注册用户名:Axure 序列号:8t3Yk/zu4cX601/seX6wBZgYRVj/lkC2PICCdO4sFKCCLx8mcCnccoylVb40lP...