建议慎用boost::weak_ptr来避免智能指针循环引用
2016-09-05 16:56
405 查看
本文摘自:
http://blog.sina.com.cn/s/blog_48d4cf2d0100uhgt.html
如果让我说C/C++相比拥有GC机制的语言如Java,开发时感觉最大的不同是什么。我想繁琐易错的内存管理肯定会排在第一位。所以自从见到基于RAII的boost智能指针实现,觉得日子好过了很多呵呵。智能指针相比GC,优点很多:效率高计算代价低即时性好。但有一个很致命的缺陷:当存在循环引用的时候将会导致内存泄露。所以还是GC舒服啊。
为了降低智能指针循环引用的可能性,boost智能指针引入了weak_ptr(各版本的智能指针实现基本上都有这个概念)。weak_ptr在构造/析构的时候不会增加/减少引用计数,由于不会增加引用计数,所以与普通智能指针(有些实现成为strong智能指针呵呵)相比,它就没法保证hold住对象,所以当要使用weak_ptr指向对象的时候,必须先将它提升为普通智能指针,如果这时候对象已经被析构,则提升会失败报错。
举个例子,假设我们要实现一个双向链表。(这里只是举个例子。链表这种结构效率很重要又很基本,当然应该使用原生指针。)如果结点的prev和next都使用strong智能指针的话,则引用计数(中括号内的数字)是这样: head -> Node1[2] <->Node2[2] <->Node3[1]。当链表析构函数让head不再指向Node1后则引用计数变成这样: Node1[1]<-> Node2[2]<->Node3[1]。发生内存泄露了。如果我们规定prev使用weak_ptr。则一开始引用计数是这样:
head-> Node1[1] <…->Node2[1] <…->Node3[1]。这样当我们让head不再指向Node1的时候将级联删除所有结点。
看起来很美好,但为何我反倒是建议慎用weak_ptr呢?这得回到实际编程中为何存在循环引用这个问题上来。为何两个对象要互相引用对方?我发现在绝大多数情况下几乎总是因为两个对象存在类似整体/部分的这种关系(要从广义角度理解整体-部分)。不管是否使用weak_ptr,甚至不管是否使用智能指针,都需要开发者正确识别出这种关系。使用weak_ptr,一般是让整体对象持有部分对象的strongptr,部分持有整体的weak ptr,这样当然是ok。但我更喜欢让整体对象持有部分对象的strongptr,部分对象则直接持有整体对象的原生指针甚至是引用(一般来说引用更好,要求构造部分对象的时候传入整体对象作为参数),然后保证整体对象的生命周期涵盖部分对象的生命周期。这两种方法相比,后者效率高(原生指针vs对象),编程简单(使用时无需提升+判断成败)。当然如果违背了整体对象生命周期涵盖部分对象生命周期的原则,会死得比较惨。但是我觉得大部分时候这是好事,因为符合Diefast
和 Die fierce原则。使用weak_ptr,一团和气之下可能掩盖了错误的对象析构顺序,而这可能又是由其他逻辑错误导致的。
http://blog.sina.com.cn/s/blog_48d4cf2d0100uhgt.html
如果让我说C/C++相比拥有GC机制的语言如Java,开发时感觉最大的不同是什么。我想繁琐易错的内存管理肯定会排在第一位。所以自从见到基于RAII的boost智能指针实现,觉得日子好过了很多呵呵。智能指针相比GC,优点很多:效率高计算代价低即时性好。但有一个很致命的缺陷:当存在循环引用的时候将会导致内存泄露。所以还是GC舒服啊。
为了降低智能指针循环引用的可能性,boost智能指针引入了weak_ptr(各版本的智能指针实现基本上都有这个概念)。weak_ptr在构造/析构的时候不会增加/减少引用计数,由于不会增加引用计数,所以与普通智能指针(有些实现成为strong智能指针呵呵)相比,它就没法保证hold住对象,所以当要使用weak_ptr指向对象的时候,必须先将它提升为普通智能指针,如果这时候对象已经被析构,则提升会失败报错。
举个例子,假设我们要实现一个双向链表。(这里只是举个例子。链表这种结构效率很重要又很基本,当然应该使用原生指针。)如果结点的prev和next都使用strong智能指针的话,则引用计数(中括号内的数字)是这样: head -> Node1[2] <->Node2[2] <->Node3[1]。当链表析构函数让head不再指向Node1后则引用计数变成这样: Node1[1]<-> Node2[2]<->Node3[1]。发生内存泄露了。如果我们规定prev使用weak_ptr。则一开始引用计数是这样:
head-> Node1[1] <…->Node2[1] <…->Node3[1]。这样当我们让head不再指向Node1的时候将级联删除所有结点。
看起来很美好,但为何我反倒是建议慎用weak_ptr呢?这得回到实际编程中为何存在循环引用这个问题上来。为何两个对象要互相引用对方?我发现在绝大多数情况下几乎总是因为两个对象存在类似整体/部分的这种关系(要从广义角度理解整体-部分)。不管是否使用weak_ptr,甚至不管是否使用智能指针,都需要开发者正确识别出这种关系。使用weak_ptr,一般是让整体对象持有部分对象的strongptr,部分持有整体的weak ptr,这样当然是ok。但我更喜欢让整体对象持有部分对象的strongptr,部分对象则直接持有整体对象的原生指针甚至是引用(一般来说引用更好,要求构造部分对象的时候传入整体对象作为参数),然后保证整体对象的生命周期涵盖部分对象的生命周期。这两种方法相比,后者效率高(原生指针vs对象),编程简单(使用时无需提升+判断成败)。当然如果违背了整体对象生命周期涵盖部分对象生命周期的原则,会死得比较惨。但是我觉得大部分时候这是好事,因为符合Diefast
和 Die fierce原则。使用weak_ptr,一团和气之下可能掩盖了错误的对象析构顺序,而这可能又是由其他逻辑错误导致的。
相关文章推荐
- 建议慎用boost::weak_ptr来避免智…
- C++11智能指针(五):shared_ptr的循环引用的问题及weak_ptr
- weak_ptr解决循环引用问题demo
- C++智能指针(三):weak_ptr--解决shared_ptr循环引用问题
- 标准库里的weak_ptr如何解决循环引用所带来的问题
- boost的shared_ptr循环引用
- 关于boost 库 shared_ptr 智能指针的循环引用【2013.10.22】
- boost的shared_ptr循环引用(1)
- 不做从strong 到weak 的转换,如何避免循环引用
- weak_ptr 弱引用打破智能指针中循环引用造成的内存泄露
- 【C++】智能指针简述(五):解决循环引用的weak_ptr
- (笔记)为何boost中boost::shared_ptr 循环引用会导致引用计数机制失效
- 使用 weak–strong dance 技术来避免循环引用
- 智能指针的模拟实现shared_ptr 循环引用 定置删除器
- 使用 weak–strong dance 技术来避免循环引用
- c++ weak ptr解除指针循环引用。
- unsafe_unretained和weak弱引用避免保留环(循环引用)区别
- std::shared_ptr 和 std::weak_ptr的用法以及引用计数的循环引用问题
- weak_ptr的作用及应用场景——shared_ptr的循环引用问题
- std::shared_ptr 和 std::weak_ptr的用法以及引用计数的循环引用问题