没有存在感的weak_ptr


C++11标准中,引入了三种智能指针:其中shared_ptr、unique_ptr露面的机会非常多,大家也越来越接受使用shared_ptr、unique_ptr来取代普通指针,省去了手动管理内存的烦恼。但是,作为智能指针三胞胎之一的weak_ptr,正如其名,存在感真是相当弱。面试中也经常发现,应聘者往往能够对shared_ptr、unique_ptr如数家珍,但是说到weak_ptr就经常语塞不知所谓了。

其实,weak_ptr的引入是为了解决老大哥shared_ptr存在的一个问题。shared_ptr相比于前辈auto_ptr虽然已经完善了不少,但是由于其基于引用计数来管理内存,导致shared_ptr在存在循环引用的情况下,会存在内存泄露!

如下图所示(高能预警,灵魂画手上线):

obj A和B均是通过shared_ptr持有的对象,同时obj A内部有一个shared_ptr指向obj B,同时obj B内部也有一个shared_ptr指向obj A。那么,obj A和obj B的引用计数将同时为2。当外部指向A和B的shared_ptr离开作用域时,引用计数各自减1,这时obj A和obj B均不会被析构,但是外部的shared_ptr已经没有了,也就是这两个对象成了孤立对象,内存泄露了~

Python也是使用引用计数作为垃圾回收的主要方式的,那Python是怎么处理的呢?

Python在引用计数之外,又引入了标记-清除(mark-sweep)法作为辅助的垃圾收集机制,专门用于对付这种循环引用导致的孤立对象。

但是,C++并没有GC,也无从找到root objects,标记清除法在智能指针场景下并不适用。

于是,没啥存在感的weak_ptr登场了~

weak_ptr只能从shared_ptr复制而来,并且不会新增shared_ptr的引用计数;weak_ptr销毁时,也不会影响到持有对象的生命周期;更重要的,weak_ptr完全没有重载operator *和operator ->,这样就完全不能直接使用weak_ptr,而只能通过weak_ptr.lock()来获得一个“临时的”shared_ptr对象(此时引用计数加1,但是因为是临时的,所以最后会减1的,放心)。

那么,使用weak_ptr的情况下,上面的图变成了:

这时,当外部的shared_ptr离开作用域销毁时,obj A的引用计数减到0,进行析构。析构导致obj A持有的obj B的shared_ptr引用计数减1,同时叠加外部shared_ptr销毁带来的引用计数减1,obj B的引用计数也将从2减为0,从而进行析构。

bingo~内存泄漏没有了~

总结

weak_ptr虽然弱,虽然没有存在感,但是,有时候还是不得不用~

留意一点:weak_ptr只能解决那些程序员明确知道会存在循环引用的情况;如果类之间的引用关系过于复杂,导致“意外地”引入了循环引用,那么weak_ptr也是没辙的,毕竟他不是GC(GC中标记-清除法就可以处理这种情况)。



推荐阅读:
使用双buffer无锁化
谈谈pImpl模式
我为什么关掉了超线程

转载请注明出处: http://blog.guoyb.com/2019/06/15/weak-ptr/

欢迎使用微信扫描下方二维码,关注我的微信公众号TechTalking,技术·生活·思考:
后端技术小黑屋

评论