智能指针(3)

目录

可能问题五:

问题分析:

答案格式:

shared_ptr的模拟实现

部分1:引用计数的设计(分考点1)

代码实现:

部分2:作为类所必须的部分(分考点2)

代码实现:

部分3:拷贝构造函数(分考点3)

代码实现1:

代码实现2:

部分4:模拟指针的行为(分考点4)

代码实现:

部分5:其他重要的成员函数(分考点5)

get() 

use_count()

部分6:定制删除器(分考点6)

代码实现1:

代码实现2:

shared_ptr自己模拟实现的最终答案展示:

代码测试:


我们接着进行智能指针的学习和试题研究。

可能问题五:

模拟实现一下weak_ptr或者unique_ptr或者shared_ptr

问题分析:

这个问题首先就明明是三个问题,因为面试时间比较短不可能连续模拟实现三个智能指针的。所以依照重要性原则,一般会重点实现shared_ptr,由于全部实现起来比较多,所以一般面试为了控制时间也会让你实现某个部分,unique_ptrweak_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逐步回忆着其中的逻辑然后按顺序写出来,我相信你一定可以完成的。

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

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

相关文章

WPF实现类似网易云音乐的菜单切换

这里是借助三方UI框架实现了&#xff0c;感兴趣的小伙伴可以看一下。 深色模式&#xff1a;​ 浅色模式&#xff1a; ​这里主要使用了以下三个包&#xff1a; MahApps.Metro&#xff1a;UI库&#xff0c;提供菜单导航和其它控件​​​​​​​ 实现步骤&#xff1a;1、使用B…

【JavaEE】——自定义协议方案、UDP协议

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 一&#xff1a;自定义协议 1&#xff1a;自定义协议 &#xff08;1&#xff09;交互哪些信息 &…

RuoYi-Vue若依 环境搭建 速成

一、若依简介 RuoYi-Vue 是一个开源的后台管理系统&#xff0c;适用于快速开发企业级应用。该平台由两部分组成&#xff1a;前端和后端。 &#xff08;1&#xff09;技术框架 前端技术&#xff1a; Vue.js: 前端框架使用 Vue.js&#xff0c;这是一种流行的JavaScript框架&a…

Python爬虫实战:抓取指定网站数据

一、前言 在互联网时代&#xff0c;数据的价值日益凸显。爬虫技术作为一种获取数据的重要手段&#xff0c;广泛应用于各种场景。本文将通过一个实例&#xff0c;介绍如何使用Python进行网站数据的抓取。 二、环境准备 Python 3.xrequests库BeautifulSoup库 三、代码实现 i…

【UE5】将2D切片图渲染为体积纹理,最终实现使用RT实时绘制体积纹理【第五篇-着色器投影-投射阴影部分】

投射阴影 最初打算将投影内容放在上一篇中&#xff0c;因为实现非常快速简单&#xff0c;没必要单独成篇。不过因为这里面涉及一些问题&#xff0c;我觉得还是单独作为一篇讲一下比较好。 原理 这里要用到的是 Shadow Pass Switch ,它可以为非不透明的材质替换阴影 某些版本…

Python3 接口自动化测试,HTTPS下载文件(GET方法和POST方法)

Python3 接口自动化测试,HTTPS下载文件(GET方法和POST方法) requests-pkcs12 PyPI python中如何使用requests模块下载文件并获取进度提示 1、GET方法 1.1、调用 # 下载客户端(GET)def download_client_get(self, header_all):try:url = self.host + "/xxx/v1/xxx-mod…

【MySQL】索引的机制、使用

在学习索引知识之前&#xff0c;我们可以先了解一下什么是索引。实际上&#xff0c;索引就是数据库中一个或多个列存储的结构&#xff0c;能够支持数据库管理系统在不扫描整张表的情况下也能查询到数据行&#xff0c;能够大大提升查询效率。举个例子&#xff0c;我们想要找到一…

WPF入门_02依赖属性

1、依赖属性主要有以下三个优点 1)依赖属性加入了属性变化通知、限制、验证等功能。这样可以使我们更方便地实现应用,同时大大减少了代码量 2)节约内存:在WinForm中,每个UI控件的属性都赋予了初始值,这样每个相同的控件在内存中都会保存一份初始值。而WPF依赖属性很好地…

upload-labs靶场Pass-13

upload-labs靶场Pass-13 查看源码 $is_upload false; $msg null; if(isset($_POST[submit])){$ext_arr array(jpg,png,gif);$file_ext substr($_FILES[upload_file][name],strrpos($_FILES[upload_file][name],".")1);if(in_array($file_ext,$ext_arr)){$temp_…

WSL2-轻量级AI训练场景最佳生产环境

WSL2 只适用于 Win 10 、Win11 在运行 AI 软件、AI 模型训练&#xff0c;Linux 是最佳的操作系统。 在运行各种软件&#xff0c;如&#xff1a;Stable Diffusion Web UI 等&#xff0c;使用 Docker 容器运行也更方便后期的快速复用&#xff0c;同样的 Docker 容器在 Linux 中…

安装vue发生异常:npm ERR! the command again as root/Administrator.

一、异常 npm ERR! The operation was rejected by your operating system. npm ERR! Its possible that the file was already in use (by a text editor or antivirus), npm ERR! or that you lack permissions to access it. npm ERR! npm ERR! If you believe this might b…

入门!Linux 常见指令及权限管理全面指南

Linux 操作系统在现代计算机应用中扮演着重要的角色&#xff0c;广泛用于服务器、桌面系统、嵌入式设备及云计算平台等领域。理解和掌握 Linux 常见指令及权限管理机制&#xff0c;是每一位系统管理员和开发人员的基础技能。本文将详细介绍 Linux 系统的基本背景、常用指令、权…

初试PostgreSQL数据库

文章目录 一、PostgreSQL数据库概述1.1 PostgreSQL的历史1.2 PostgreSQL安装1.3 安装PostgreSQL二、PostgreSQL起步2.1 连接数据库2.1.1 SQL Shell2.1.2 执行SQL语句2.2 pgAdmin 42.2.1 打开pgAdmin 42.2.2 查找数据库2.2.3 打开查询工具2.2.4 执行SQL语句三、实战小结文章目录…

【leetcode练习·二叉树】用「遍历」思维解题 III

本文参考labuladong算法笔记[【强化练习】用「遍历」思维解题 III | labuladong 的算法笔记] 437. 路径总和 III | 力扣 | LeetCode | 给定一个二叉树的根节点 root &#xff0c;和一个整数 targetSum &#xff0c;求该二叉树里节点值之和等于 targetSum 的 路径 的数目。 路…

c语言基础程序——经典100道实例(二)

前面 52 题可以看下 《c语言基础程序——经典100道实例。》 c语言基础程序——经典100道实例 053&#xff0c;按位异或 ^054&#xff0c;取数右端4~7位055&#xff0c;按位取反~056&#xff0c;画圆形057&#xff0c;画直线058&#xff0c;画矩形059&#xff0c;画椭圆060&…

Git上传命令汇总

进入企业&#xff0c;每日需要上传执行用例记录到gitlab平台上&#xff0c;本文记录了常用git上传命令&#xff0c; 并用github演示。 1、本地建立分支&#xff0c;克隆远程仓库 在gitlab中&#xff0c;每个人需要创建自己的分支&#xff0c;一般以自己的名字命名&#xff0c;…

FineReport 页面设置

点击菜单栏中的「模板>页面设置」&#xff0c;弹出页面设置对话框&#xff0c;就可以对当前 sheet 进行页面设置&#xff0c;一个报表的每个 sheet 页面设置可以不同&#xff1a; 1 方向 指纸张方向&#xff0c;通常与打印结合使用。A4 纸横向预览效果和纵向预览效果 2、…

HCIP-HarmonyOS Application Developer 习题(十四)

&#xff08;多选&#xff09;1、HarmonyOs为应用提供丰富的Al(Artificial Intelligence)能力&#xff0c;支持开箱即用。下列哪些是它拥有的AI能力? A、通用文字识别 B、词性标注 C、实体识别 D、语音播报 答案&#xff1a;ABCD 分析&#xff1a; AI能力简介二维码生成根据开…

为什么软件维护成本比软件的开发成本高?

很多项目的软件维护成本比软件的开发成本高出很多 一、需求变更频繁 业务需求变化 随着市场环境的变化和业务的发展&#xff0c;客户的需求可能会不断调整和改变。例如&#xff0c;企业的业务模式发生调整&#xff0c;需要软件系统增加新的功能模块或对现有功能进行重大修改…

为什么一条Java命令,JVM就可以执行Java程序了(串联JVM面试知识点)

文章目录 前言从面试题说起JVM做了哪些事&#xff1f;“翻译”的工作不仅仅“翻译” JVM 各部件如何协同工作&#xff1f;类加载器先工作执行引擎开始工作执行引擎工作模式Main方法什么时候被执行&#xff1f; 运行时数据区域开始工作线程私有的空间大名鼎鼎的堆内存 就这么一直…