智能指针的原理及简单模拟实现(C++)

概念

正如上一篇所说如果要对可能发生异常的都用catch(...)捕获一下,那代码就会非常冗余(如new可能也会抛出异常)。这就是智能指针出现的缘由

RAII是一种管理资源的类的设计思想------利用对象的生命周期管理指定动态资源(内存、文件指针、网络连接、互斥锁等),以防资源泄漏。详细点说就是RAII通过得到动态资源时将资源委托给一个对象,保持资源在对象的生命周期内始终保持有效,当对象生命周期结构系统自动调用的析构函数中释放资源,有效防止了资源的泄漏问题

原理

C++实现的智能指针在C++标准库的<memory>中,我们一起来学习一下

在学习之前我们需要先明确我们会有暂时持有访问某个资源的需求,如需要智能指针对象为实参,要传给一个函数的形参以访问那个资源。如果我们只传这个指向资源的指针或者智能指针引用,都无法覆盖所有场景,需要考虑到智能指针的拷贝,是深拷贝还是浅拷贝呢,因为我们是为了管控资源,肯定不可以深拷贝,如果浅拷贝,拷贝出来的对象析构,那实参的对象再析构就会报错了

首先是auto_ptr,它是C++98提出来的,设计的比较糟糕,当这个智能指针要拷贝另一个对象时会直接将管理权送给复制的对象,相当于被复制的对象悬空了(原auto_ptr类对象内部就变成了空指针),我们无法再用。设计的十分糟糕,在C++11出现之前很多公司就明令禁止auto_ptr,C++11也增加了新的智能指针语法,这个语法就不要使用了

unique_ptr:智能指针对象不能拷贝(unique的汉语是唯一),只能移动。如果不需要拷贝时建议使用

shared_str:智能指针对象可以拷贝,也可以移动(shared的汉语是共享)。需要拷贝时就使用它。它的底层是增加了引用计数这一概念实现了多智能指针共同管理统一资源。引用计数------如果多一个对象指向此资源这个引用计数就++,如果此对象析构了引用计数--,直到引用计数等于0时此时没有对象指向此资源,这个资源就要释放

weak_ptr创造出来是为了解决shared_ptr的一个问题(文章后面再介绍),它不是智能指针(它不能管理资源)

智能指针默认是通过delete来释放资源,但也可能会有其他形式的动态资源如C语言中的malloc等函数和文件操作、以及new\[\],都无法简单通过delete来释放,C++为智能指针增加了删除器概念,删除器本质上是一个可调用对象(如仿函数、lamda、函数),不过shared_ptr是在构造时给删除器,而unique_ptr是在类模板参数处给删除器。由于new\[\]使用频繁,C++将unique_ptr和shared_ptr都实现了特化,这里的特化与前面讲的"shared_ptr的删除器要传给构造"要区分开,是不同的方法:

unique_ptr<Date\[\]> up1(new Date5);

shared_ptr<Date\[\]> sp1(new Date5);

shared_ptr除了支持用指向资源的指针构造,还支持make_shared用初始化资源对象的值直接构造

我们可以大致将make_shared函数理解如下,设计成可变参数模板是为了当类型如果是一个有较多成员变量的类时,依然可用

shared_ptr和unique_ptr都支持了operator bool的类型转换,如果智能指针对象是一个空对象没有管理资源,则返回false,否则返回true,意味着我们可以直接把智能指针对象给if判断是否为空

shared_ptr和unique_ptr都得构造函数都使用explicit修饰,防止普通指针隐式类型转换成智能指针对象,普通指针隐式类型转换成智能指针对象看起来很方便,但实际上很危险,因为普通指针一旦被自动交给智能指针管理,所有权关系可能变得非常不清楚

模拟实现

难点:1、引用计数的类型是什么?2、删除器加入到shared_ptr的构造函数后,怎么让析构函数可以复用?让删除器成为成员变量,那么此成员变量是什么类型,而且其默认删除器怎么设置?

下图只是简单模拟实现基本原理,引用计数若是搭配weak_ptr,实现起来会更复杂,有兴趣的可以去查阅源码

但是shared_ptr在这样的场景(循环引用)中会存在问题:

这样会出现内存泄漏问题:

sp1析构,指向左侧链表结点的引用计数为1。sp2析构,指向右侧链表结点的引用计数为1。左侧结点如果想释放需要右侧结点的_prev释放,右侧结点的_prev若要释放需要右侧结点释放,右侧结点若想释放需要左侧结点的_next释放,左侧结点的_next若要释放需要左侧结点释放。哈哈,是不是闭环了,不倒翁。此时内存就会泄漏。C++11为了解决此问题创造出了weak_ptr

weak_ptr不支持RAll(不会管理资源),也不支持访问资源,只支持绑定到shared_ptr,绑定到shared_ptr时,不增加shared_ptr的引|用计数,此时就可以解决上述的循环引用问题

为什么不管理资源呢?它不会增加引用计数,若它还指向资源,如果资源已经释放了,那么他去访问资源就是很危险的。weak_ptr有接口expired检查指向的资源是否过期(资源是否释放),use_count也可获取shared_ptr的引用计数,weak_ptr想访问资源时,可以调用lock返回一个管理资源的shared_ptr,如果资源已经被释放,返回的shared_ptr是一个空对象,如果资源没有释放,则通过返回的shared_ptr访问资源是安全的

改造成这样循环引用的问题就解决了

shared_ptr的还存在线程安全问题

这里我还没学到,等我学完了再来介绍

相关推荐
l1t1 小时前
DeepSeek总结的OpenZL压缩变换器
c++·算法
en.en..1 小时前
C语言---宏
c语言·开发语言
郝学胜-神的一滴1 小时前
C++ Templates 01:拨开迷雾,读懂C++模板经典著作
开发语言·c++·程序人生·游戏·游戏引擎·软件构建
坤坤子吖1 小时前
算法学习——高精度加减乘除
c++·笔记·学习·算法
言乐61 小时前
Python实现关税自动计算模型
开发语言·python·django·virtualenv·pygame
朝朝辞暮i1 小时前
C++ 第 14 课:引用 &
开发语言·c++·算法
白山编程大哥2 小时前
适合初学者练习的 C/C++ 开源项目推荐
c语言·c++·开源
梅雅达编程笔记2 小时前
04-Python CSV数据保存与翻页抓取
开发语言·爬虫·python·pandas·数据采集·csv
C++ 老炮儿的技术栈2 小时前
有符号变量与无符号变量的区别
服务器·开发语言·前端·数据结构·c++·算法·c