建议慎用boost::weak_ptr来避免智…

    如果让我说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,一般是让整体对象持有部分对象的strong ptr,部分持有整体的weak ptr,这样当然是ok。但我更喜欢让整体对象持有部分对象的strong ptr,部分对象则直接持有整体对象的原生指针甚至是引用(一般来说引用更好,要求构造部分对象的时候传入整体对象作为参数),然后保证整体对象的生命周期涵盖部分对象的生命周期。这两种方法相比,后者效率高(原生指针vs对象),编程简单(使用时无需提升+判断成败)。当然如果违背了整体对象生命周期涵盖部分对象生命周期的原则,会死得比较惨。但是我觉得大部分时候这是好事,因为符合Die fast 和 Die fierce 原则。使用weak_ptr,一团和气之下可能掩盖了错误的对象析构顺序,而这可能又是由其他逻辑错误导致的。

你可能感兴趣的:(c/cpp)