智能指针(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逐步回忆着其中的逻辑然后按顺序写出来,我相信你一定可以完成的。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

河北工业大学《2021年+2020年980自动控制原理真题》 (完整版)

本文内容&#xff0c;全部选自自动化考研联盟的&#xff1a;《河北工业大学980自控考研资料》的真题篇&#xff0c;真题年份为2004-最新一年。后续会持续更新更多学校&#xff0c;更多年份的真题&#xff0c;记得关注哦~ 目录 2021年真题 2020年真题 Part1&#xff1a;2021年…

Data+AI下的数据湖和湖仓一体发展史

DataAI下的数据湖和湖仓一体发展史 前言数据湖的“前世今生”AI时代的救星&#xff1a;湖仓一体湖仓一体实践演进未来趋势&#xff1a;智能化、实时化结语 前言 数据湖&#xff1f;湖仓一体&#xff1f;这是什么高科技新名词&#xff1f; 别急&#xff0c;我们慢慢聊。想象一…

DBeaver导出数据表结构和数据,导入到另一个环境数据库进行数据更新

在工作中&#xff0c;我们会进行不同环境之间数据库的数据更新&#xff0c;这里使用DBeaver导出新的数据表结构和数据&#xff0c;并执行脚本&#xff0c;覆盖另一个环境的数据库中对应数据表&#xff0c;完成数据表的更新。 一、导出 右键点击选中想要导出的数据表&#xff0…

parent参数

一、parent参数 parent参数除了有之前父窗口的界面效果外&#xff0c;还体现了Qt的内存管理策略。parent参数的对象是当前创建的对象的父对象。因此在Qt中存在父对象与子对象的概念&#xff0c;需要注意的是&#xff0c;此处的父子关系与继承无关&#xff0c;至于parent参数有关…

UNION 联合查询

1.UNION ALL联合查询 同样为了演示方便&#xff0c;先向 teacher 表插入多条测试数据&#xff1a; INSERT INTO teacher (name,age,id_number,email) VALUES (姓名一,17,42011720200604077X,NULL), (姓名二,18,42011720200604099X,123qq.com), (姓名三,19,42011720200604020X…

Web 应用防火墙(WAF)

在现代Web应用开发中&#xff0c;Nginx作为反向代理的架构被广泛采用。这种架构具备高性能、易扩展的特点&#xff0c;但也带来了Web层的安全挑战。Web应用防火墙&#xff08;WAF&#xff09;作为专门防御Web应用层攻击的安全措施&#xff0c;能够为此架构增加一层强有力的保护…

服务器托管的优缺点有哪些?

由于数字化程度不断提高&#xff0c;服务器在日常业务中发挥着越来越重要的作用。在大多数情况下&#xff0c;服务器由公司自己维护和管理。但对于一些公司来说&#xff0c;托管服务器(将这些任务交给专业人员)是更好的选择。 关于服务器的优缺点&#xff0c;有一点是明确的&am…

Centos7 安装升级最新版Redis7.4.1

1. 前言 今天阿里云云盾检测出一个redis低版本的漏洞,需要升级到稳定高版本修复漏洞,升级过程遇到了一些坑,特记录分享给大家,原服务器默认yum源安装的gcc 是4.8.5 ,默认安装redis是 3.2.12(如下图): 2.升级GCC 升级新版redis需要更高级的gcc支持,这里我们就选择升级…

打包使用pythn编写的maya插件,使用pyeal打包

1.安装python,注意版本一定要和maya上面的python解释器版本一致 2.安装pyeal使用pycharm或者maya自带的python解释器mayapy.exe 3.如果有别的库&#xff0c;下载安装到你需要的文件夹中&#xff1a; 使用mayapy: "D:\AnZhuangBao\maya2022\2022\maya2022AZ\Maya2022\bin\m…

第二百八十八节 JPA教程 - JPA查询连接OrderBy示例

JPA教程 - JPA查询连接OrderBy示例 以下代码显示如何使用ORDER BY子句和连接条件。 List l em.createQuery("SELECT e " "FROM Project p JOIN p.employees e " "WHERE p.name :project " "ORDER BY e.name").setParameter("pr…

国产AI逆袭!零一万物新模型Yi-Lightning超越 GPT-4o

近日&#xff0c;由全球千万用户盲测投票产生的 AI 模型排行榜公布&#xff0c;国产 AI 模型“Yi-Lightning”逆袭&#xff0c;超越了此前长期占据榜首的 GPT-4。 “Yi-Lightning”模型由国内知名 AI 公司零一万物研发&#xff0c;在多个分榜中均名列前茅&#xff0c;其中数学…

R语言机器学习算法实战系列(六)K-邻近算法 (K-Nearest Neighbors)

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍教程下载数据加载R包导入数据数据预处理数据描述数据切割调节参数构建模型预测测试数据评估模型模型准确性混淆矩阵模型评估指标ROC CurvePRC Curve保存模型总结系统信息介绍 K-邻…

从传统到智能,从被动监控到主动预警,解锁视频安防平台EasyCVR视频监控智能化升级的关键密钥

视频监控技术从传统监控到智能化升级的过程是一个技术革新和应用场景拓展的过程。智能视频监控系统通过集成AI和机器学习算法&#xff0c;能够实现行为分析、人脸识别和异常事件检测等功能&#xff0c;提升了监控的准确性和响应速度。这些系统不仅用于传统的安全防护&#xff0…

KPaaS集成平台中怎么创建数据可视化大屏

KPaaS集成平台的数据可视化大屏是什么&#xff1f; 在KPaaS业务集成扩展平台中&#xff0c;数据大屏是一种数据可视化展示工具&#xff0c;它可以帮助企业将复杂的数据以直观、易理解的方式呈现出来&#xff0c;从而提高数据的可读性和价值。数据大屏的主要特点包括&#xff1…

PROFINET开发或EtherNet/IP开发嵌入式板有用于工业称重秤

这是一个真实案例&#xff0c;不过客户选择不透露其品牌名称。稳联技术的嵌入式解决方案助力工业称重设备制造商连接至任意工业网络。多网络连接使得称重设备能够轻松接入不同的控制系统&#xff0c;进而加快产品的上市时间。 我们找到了稳联技术的解决方案。他们成熟的技术与专…

【厦门大学附属第一医院(互联网医院)-注册安全分析报告-无验证方式导致安全隐患】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 1. 暴力破解密码&#xff0c;造成用户信息泄露 2. 短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉 3. 带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造…

实验23:DA呼吸灯实验

电路硬件: 实现功能: 代码: public.h #ifndef _public_H #define _public_H#include "reg52.h" //#include "key.h"typedef unsigned int u16; typedef unsigned char u8;void delay_10us(u16 n); void delay_ms(u16 ms);#endif public.c #include …

线性代数学习

1.标量由只有一个元素的张量表示 import torchx torch.tensor([3,0]) y torch.tensor([2,0])x y, x * y, x / y, x**y 2.可以将向量视为标量值组成的列表 x torch.arange(4) x 3.通过张量的索引访问任一元素 x[3] 4.访问张量长度 len(x) 5.只有一个轴的张量&#xff0c…

单片机常见的存储器

常见的存储器&#xff1a; 易失性存储器RAM RAM&#xff0c;随机访问存储器(Random Access Memory)&#xff0c;易失性存储器&#xff0c;它可以随时读写&#xff0c;而且速度很快&#xff0c;通常作为操作系统或其他正在运行中的程序的临时数据存储媒介。它的作用是当开机后系…

bml上部署yolov8

第一步 #第二步 在这里插入代码片git clone https://github.com/ultralytics/ultralytics.git一定要创建一个storage来专门存放yolov8&#xff0c;放在其他路径容易出错。 如果下载之后storage路径里面没有ultralytics&#xff0c;那是没有下载成功&#xff0c;多下载几次就行…