分享

C 智能指针如何实现智能?

 InfoRich 2022-04-11
 点击上方“AI大道理”,选择“置顶”公众号
重磅干货,细致入微AI大道理
 ——————

指针是一个变量,其值为另一个变量的地址,即内存位置的直接地址。
指针就像是一个目录,其值就像页码,页码指向某一个的内容。

Image普通指针

问题1:内存泄露问题
问题描述:
C++没有自动回收内存的机制,每次new出来的动态内存必须手动delete。
如果忘记delete,资源未被释放,将导致内存泄露。

Image

问题2:迷途指针
问题描述:
多个指针指向同一个对象时,有一个指针释放了,但是其他指针并不知道这个情况,若继续使用那个被释放的指针将出错。

Image

问题3:野指针

没有经过初始化就直接拿来用的指针,将出错。

Image


Image智能指针

智能指针是一个模板类,用来存储指针(指向动态分配对象的指针)。

智能指针是通过基本类型(模板类)指针构造类的对象,指针本身就是一个自定义的对象。
当此对象被销毁时,即调用此对象的析构函数,释放此指针。
智能指针其实就是对普通指针的封装,封装成一个类。通过重载*和->两个运算符使得智能指针表现得就像普通指针一样。
智能指针遵从RAII思想。

RAII思想:
能够像指针一样(运算符重载,解引用,指向对象成员)。
对资源进行封装和管理。
RAII思想(资源分配及初始化)(Resource Acquisition Is Initialization)
  • 定义一个类来封装资源的分配与释放。
  • 构造函数中完成资源的分配及初始化。
  • 析构函数中完成资源的清理,可以保证资源的正确初始化和释放
  • 如果对象是用声明的方式在栈上创建局部对象,那么RAII机制就会正常工作,当离开作用域对象会自动销毁而调用析构函数释放资源。

智能指针主要有:
auto_ptr:弃用的指针
unique_ptr:独占智能指针
shared_ptr: 共享智能指针
weak_ptr: 弱智能指针
Imageauto_ptr
实现原理:
auto_ptr事实上是一个类,在构造对象时获取对象的管理权,无需考虑释放动态内存开辟的空间,在析构函数中直接释放,不会出现内存泄漏的问题。
缺陷:
1)一个指针变量指向的空间不能由两个auto_ptr管理,不然会析构两次,使程序崩溃。

Image


2)auto_ptr的拷贝构造,将源指针的管理权交给目标指针,会使得源指针悬空,解引用是会出现很多问题。

Image


3)auto_ptr不能用来管理数组,析构函数中用的是delete。

Imageunique_ptr
unique_ptr解决了auto_ptr编译不报错的问题。

Image

unique_ptr是auto_ptr的继承者,对于同一块内存只能有一个持有者。
unique_ptr和auto_ptr唯一区别就是unique_ptr不允许赋值操作,也就是不能放在等号的右边,这一定程度避免了一些误操作导致指针所有权转移。
unique_ptr的核心特点就如它的名字一样,它拥有对持有对象的唯一所有权,即两个unique_ptr不能同时指向同一个对象。
nique_ptr所持有的对象只能通过转移语义将所有权转移到另外一个unique_ptr。
unique_ptr本身拥有的方法主要包括:
  • get()获取其保存的原生指针,尽量不要使用
  • bool()判断是否拥有指针
  • release()释放所管理指针的所有权,返回原生指针。但并不销毁原生指针。
  • reset()释放并销毁原生指针。如果参数为一个新指针,将管理这个新指针

Imageshared_ptr
unique_ptr不能多个指针指向同一个资源,而shared_ptr可以。
实现原理:
每次复制或者多一个共享内存资源的shared_ptr时,计数+1;
每次释放shared_ptr时,计数-1;
当shared_ptr计数为0时,证明所有指向同一资源的shared_ptr都全部释放了。

shared_ptr 需要维护的信息有两部分:

  • 指向共享资源的指针。

  • 引用计数等共享资源的控制信息——实现上是维护一个指向控制信息的指针。

常规的创建一个 shared_ptr:

Image

为了构建一个std::shared_ptr对象,却进行了两次内存分配,而且第二次内存分配分配的内存还比较小,这一方面会影响程序性能,另一方面还会大大增加内存碎片产生的可能性。

复制一个 shared_ptr :

Image

std::make_shared创建一个 shared_ptr:

std::make_shared的精妙之处就在于,它将std::shared_ptr构造中的两次内存分配降低到了一次。这会对提供程序性能和降低内存碎片都有帮助。

Image

shared_ptr本身拥有的方法主要包括:

  • get() 获取其保存的原生指针,尽量不要使用
  • bool() 判断是否拥有指针
  • reset() 释放并销毁原生指针。如果参数为一个新指针,将管理这个新指针

  • unique() 如果引用计数为 1,则返回 true,否则返回 false

  • use_count() 返回引用计数的大小

循环引用问题:

但是,shared_ptr也有一个致命的缺点,就是会出现循环引用
Shared_ptr 会出现循环引用的情况:

Image


Image


Image

调用析构后,还有互相引用的指针计数没有减掉。
要释放sp2,就需要先释放sp1->_next。
要释放sp1->_next, 就需要先释放sp1。
要释放sp1,就需要先释放sp2->_prev。
要释放sp2->_prev,就需要先释放sp2。
这样一来,就陷入了一个无限的循环当中,谁都释放不掉。
Image

如何解决?

Imageweak_ptr

std::weak_ptr 要与 std::shared_ptr 一起使用。
弱引用指针就是没有“所有权”的指针。
有时候我们只是想找个指向这块内存的指针,但我们不想把这块内存的生命周期与这个指针关联,这种情况下,弱引用指针就代表“我指向这东西,但这东西什么时候释放不关我事儿。
weak_ptr是辅助shared_ptr的存在,它只提供对管理对象的访问手段,同时可以实时动态知道所指向的对象是否存活,起到观察者的作用。
weak_ptr不具有普通指针的行为,没有重载operator *和->,只能想像旁观者一样观测资源的使用情况。
实现原理:
计数区域引进新的计数变量weak_count,来作为弱引用指针。
weak_ptr的构造和析构不会引起shared_ptr的计数的增加和减少,只会引起weak_count的增加和减少。

双计数:
资源的释放只取决shared的计数,当计数为0时,释放资源,weak_ptr不控制资源的生命周期。
计数区域的释放取决于shared计数和weak计数,当两者都为0时,才释放计数区域。

weak_ptr本身拥有的方法主要包括:

  • expired() 判断所指向的原生指针是否被释放,如果被释放了返回 true,否则返回 false

  • use_count() 返回原生指针的引用计数

  • lock() 返回 shared_ptr,如果原生指针没有被释放,则返回一个非空的 shared_ptr,否则返回一个空的 shared_ptr

  • reset() 将本身置空


循环引用问题的解决:

Image


Image

Image


调用析构后,sp1和sp2成功释放。
要释放sp1->_next, 就需要先释放sp1,已经释放,所以sp1->_next释放。
要释放sp2->_prev,就需要先释放sp2,已经释放,所以sp2->_prev释放。
这样一来,就都释放掉了。

Image


Image总结

unique_ptr:内存的所有者或者说管理者必须是唯一的。如果进入不同的模块或者调用者,那么执行所有权转移。

shared_ptr: 内存由多个指针变量共同使用,共同拥有内存的所有权。但是必须杜绝循环拷贝!

weak_ptr: 对内存的使用仅仅是访问而已,不涉及其生命周期的管理。

Image




Image
 

 ——————

浅谈则止,细致入微AI大道理
扫描下方“AI大道理”,选择“关注”公众号
—————————————————————

Image 

—————————————————————
投稿吧   | 留言吧

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多