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

C++ 智能指针如何实现智能?,第1张

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

本文来自公众号“AI大道理”。

这里既有AI,又有生活大道理,无数渺小的思考填满了一生。

指针是一个变量,其值为另一个变量的地址,即内存位置的直接地址。

指针就像是一个目录,其值就像页码,页码指向某一个的内容。

1、普通指针

问题1:内存泄露问题

问题描述:

C++没有自动回收内存的机制,每次new出来的动态内存必须手动delete。

如果忘记delete,资源未被释放,将导致内存泄露。

问题2:迷途指针

问题描述:

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

问题3:野指针

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

2、智能指针

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

智能指针是通过基本类型(模板类)指针构造类的对象,指针本身就是一个自定义的对象。

当此对象被销毁时,即调用此对象的析构函数,释放此指针。

智能指针其实就是对普通指针的封装,封装成一个类。通过重载*和->两个运算符使得智能指针表现得就像普通指针一样。

智能指针遵从RAII思想。

RAII思想:

能够像指针一样(运算符重载,解引用,指向对象成员)。

对资源进行封装和管理。

RAII思想(资源分配及初始化)(Resource Acquisition Is Initialization)

定义一个类来封装资源的分配与释放。

构造函数中完成资源的分配及初始化。

析构函数中完成资源的清理,可以保证资源的正确初始化和释放

如果对象是用声明的方式在栈上创建局部对象,那么RAII机制就会正常工作,当离开作用域对象会自动销毁而调用析构函数释放资源。

智能指针主要有:

auto_ptr:弃用的指针

unique_ptr:独占智能指针

shared_ptr: 共享智能指针

weak_ptr: 弱智能指针

3、auto_ptr

实现原理:

auto_ptr事实上是一个类,在构造对象时获取对象的管理权,无需考虑释放动态内存开辟的空间,在析构函数中直接释放,不会出现内存泄漏的问题。

缺陷:
1)一个指针变量指向的空间不能由两个auto_ptr管理,不然会析构两次,使程序崩溃。

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

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

4、unique_ptr

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

unique_ptr是auto_ptr的继承者,对于同一块内存只能有一个持有者。

unique_ptr和auto_ptr唯一区别就是unique_ptr不允许赋值 *** 作,也就是不能放在等号的右边,这一定程度避免了一些误 *** 作导致指针所有权转移。

unique_ptr的核心特点就如它的名字一样,它拥有对持有对象的唯一所有权,即两个unique_ptr不能同时指向同一个对象。

nique_ptr所持有的对象只能通过转移语义将所有权转移到另外一个unique_ptr。

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

get()获取其保存的原生指针,尽量不要使用

bool()判断是否拥有指针

release()释放所管理指针的所有权,返回原生指针。但并不销毁原生指针。

reset()释放并销毁原生指针。如果参数为一个新指针,将管理这个新指针

5、shared_ptr

unique_ptr不能多个指针指向同一个资源,而shared_ptr可以。

实现原理:

每次复制或者多一个共享内存资源的shared_ptr时,计数+1;

每次释放shared_ptr时,计数-1;

当shared_ptr计数为0时,证明所有指向同一资源的shared_ptr都全部释放了。

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

指向共享资源的指针。

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

常规的创建一个 shared_ptr:

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

复制一个 shared_ptr :

std::make_shared创建一个 shared_ptr:

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

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

get() 获取其保存的原生指针,尽量不要使用

bool() 判断是否拥有指针

reset() 释放并销毁原生指针。如果参数为一个新指针,将管理这个新指针

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

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

循环引用问题:

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

调用析构后,还有互相引用的指针计数没有减掉。

要释放sp2,就需要先释放sp1->_next。

要释放sp1->_next, 就需要先释放sp1。

要释放sp1,就需要先释放sp2->_prev。

要释放sp2->_prev,就需要先释放sp2。

这样一来,就陷入了一个无限的循环当中,谁都释放不掉。

如何解决?

6、weak_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() 将本身置空

循环引用问题的解决:

调用析构后,sp1和sp2成功释放。

要释放sp1->_next, 就需要先释放sp1,已经释放,所以sp1->_next释放。

要释放sp2->_prev,就需要先释放sp2,已经释放,所以sp2->_prev释放。

这样一来,就都释放掉了。

7、总结

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

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

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


 

 ——————

浅谈则止,细致入微AI大道理

扫描下方“AI大道理”,选择“关注”公众号

—————————————————————

 

 

 

—————————————————————

投稿吧   | 留言吧

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5714384.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存