本文来自公众号“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大道理”,选择“关注”公众号
—————————————————————
—————————————————————
投稿吧 | 留言吧
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)