模拟实现Boost库智能指针shared_ptr

2017-04-04 13:08 阅读 647 次 评论 1 条

从之前智能指针auto_ptr的极度不安全,到scoped_ptr的无法赋值拷贝,我们看到的是功能的缺失,安全性的思考,那么是否有一种智能指针能两者兼得呢?shared_ptr无疑成为了首选之选,它是最像指针的智能指针,也是Boost库中最有价值、最重要的组成部分之一。

shared_ptr的设计思想

共享指针,核心思想是通过引用计数来实现的,引入一个变量指针pCount来指向一块内存空间,当调用构造函数、赋值运算符的重载以及拷贝构造函数时都会对其引入的计数器pCount+1,只有当仅有一个指针指向这块空间时,即pCount=1时,才可以释放空间,否则引用计数pCount-1。

大家从这一点想到了什么?没错,之前STL中的string类的写时拷贝也是引入的这个思想,两者有异曲同工之妙,具体看源代码。

存在缺陷的版本源代码

问题1->循环引用

比如下面的程序,我们定义一个双向循环链表的结构体,在结构体外定义两个节点,让p1的后继指向p2,p2的前驱指向p1,那么程序是否会正常运行呢?

当资源要释放时,p1节点释放的前提是p2释放,而p2的释放又依赖于p1,就形成了一个互相等待的局面,上升到操作系统的话,就等于进程之间形成了死锁,只不过这里是资源释放的依赖关系,而操作系统是资源竞争的关系。最终程序形成了循环引用,两个节点都无法释放资源,内存泄漏也就顺理成章。

解决方案(循环引用)

为了解决shared_ptr带来的循环引用的问题,我们引入了weak_ptr这个弱引用指针,它不能单独使用,用来辅助shared_ptr。weak_ptr智能指针会对引用计数做出特殊的处理,对上述情况不在加1。比如下面的代码:

weak_ptr是一种不控制所指向对象生存周期的智能指针,它指向一个由shared_ptr管理的对象,将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被销毁,即使有weak_ptr指向对象,对象还是会被释放。

你会发现当我们将链表结构体中的前驱指针与后继指针定义为weak_ptr时,引用计数不仅没有增加,而且也成功调用了两个节点的析构函数,防止了内存泄漏以及循环引用计数的问题。

问题2->线程安全性问题

shared_ptr 对象提供与内建类型一样的线程安全级别。一个 shared_ptr 实例可以同时被多个线程“读”(仅使用不变操作进行访问)。 不同的 shared_ptr 实例可以同时被多个线程“写入”(使用类似 operator= 或 reset 这样的可变操作进行访问)(即使这些实 例是拷贝,而且共享下层的引用计数),任何其它的同时访问的结果会导致未定义行为。总结一下主要有3个方面:

①同一个shared_ptr被多个线程“读”是安全的。

②同一个shared_ptr被多个线程“写”是不安全的。

③共享引用计数的不同的shared_ptr被多个线程”写“ 是安全的。

问题3->内存泄漏问题

解决了循环引用问题,尴尬的是还有一个内存泄漏问题,也就是说,当我们用malloc申请出来的空间,是无法释放的,因为malloc申请的空间只能用free去释放。当我们打开一个文件的时候,相应的也会维护一个文件指针,当程序运行完毕的时候,就必须关闭这个文件,否则会造成内存泄漏。

解决方案(内存泄漏)

定置删除器。如果指针是一个指向文件类型的,在析构函数中只需要关闭文件即可,而不是释放空间。如果空间是malloc出来的,那么析构函数中必须要调用free来释放资源,而不是delete来释放空间。这里我们会用到仿函数,具体的实现见代码。

删除器版本的shared_ptr

智能指针总结

如果程序要使用多个指向同一个对象的指针,应该选择shared_ptr。这样的情况包括:有一个指针数组,并使用一些辅助指针来表示特定的元素,如最大的元素和最小的元素;两个对象包含都指向第三个对象的指针;STL容器包含指针。

如果程序不需要多个指向同一个对象的指针,则可使用scoped_ptr(unique_ptr)。如果函数使用new分配内存,并返回指向该内存的指针,将其返回类型声明为scoped_ptr(unique_ptr)是不错的选择。这样,所有权将转让给接受返回值的unique_ptr,而该智能指针将负责调用delete。

版权声明:本文著作权归原作者所有,欢迎分享本文,谢谢支持!
转载请注明:模拟实现Boost库智能指针shared_ptr | 术与道的分享
分类:编程素养 标签:, ,
1024do.com导航_术与道导航平台

发表评论


表情