C++内存管理:weak_ptr


解决什么问题

举例:双向链表

已经有了shared_ptrunique_ptr后,为什么还要提出weak_ptr呢?以双向链表为例说明,各种引用关系如下图所示:

链表中的引用关系

class Node{
public:
    Node(){
        cout << "call Node constructor" << endl;
    }
    ~Node(){
        cout << "call Node destructor" << endl;
    }
    int data;
    shared_ptr<Node> pre;
    shared_ptr<Node> next; 
};

int main(){
    shared_ptr<Node> sp1(new Node());
    shared_ptr<Node> sp2(new Node());
    // == 1 
    cout<<sp1.use_count()<<endl;
    cout << sp2.use_count() << endl;
    // ==
    
    // == 2 
    // sp1 -> next = sp2;
    // sp2 -> pre= sp1;
    
    // cout<<sp1.use_count()<<endl;
    // cout << sp2.use_count() << endl;
    // ==
}

现象

在上述的代码中,只开启1对应的代码时,得到如下的输出:

只开启1对应的代码

当1和2都开启后,得到如下的输出:

当1和2都开启对应的代码

发现当开启了2之后,析构函数没有被调用,说明出现了对象没有被销毁,出现了内存泄露。

原因

出现上述现象的原因在于,对于双向链表而言,当要释放node2内存空间时,需要指向node2的shared_ptr的reference count为0,为了实现这个目的,需要将node1.next释放,也就需要释放node1。

进一步,如果要释放node1,需要node2.pre被释放,也就需要释放node2。最后变成了:

  • 要释放node2,先要释放node1;
  • 要释放node1,先要释放node2;

循环引用的问题。

weak_ptr的解决方式

只需要将Node中的next和pre变为weak_ptr即可,其余仍然不变。

class Node{
public:
    Node(){
        cout << "call Node constructor" << endl;
    }
    ~Node(){
        cout << "call Node destructor" << endl;
    }
    int data;
    weak_ptr<Node> pre;
    weak_ptr<Node> next; 
};

得到如下的输出:

使用weak_ptr后的输出

发现能够成功调用到析构函数,说明内存被正确的释放了。

其中的原因在于,此时在双向链表中,node1和node2对应的shared_ptr的reference count均为1,没有形成环,也就没有循环引用的问题,可以自由释放。

特点

weak_ptr具有如下的特点:

  • 不控制所指向对象的lifetime,也不负责释放该对象;

    do not control the lifetime of the object to which it points.

    • 所指向的对象由shared_ptr负责管理。
  • 由于不负责管理该对象,因此其不会改变shared_ptrreference count

    因为不会改变object的引用计数,也不负责其的释放,因此当shared_ptr的引用计数为0后,即便存在weak_ptr指向该object,也会毫不犹豫的执行object的释放。

因此,总结来看,weak_ptr不负责、不管理所指向的object,只是一个“旁观者”。

初始化

weak_ptr的初始化要依赖shared_ptr

auto p = make_shared<int>(42);
// wp weakly shares with p; use count in p is unchanged
// wp and p point to the same object
weak_ptr<int> wp(p);

确保object存在

因为当shared_ptr所指向的对象被删除后,weak_ptr也无效了,不能通过指针访问该对象了。因此,为了能够使得weak_ptr有效地访问该对象,必须进行判定。

if (shared_ptr<int> np = wp.lock()) { // 
// inside the if, np shares its object with p 
}
  • lock returns a shared_ptr to the shared object.

只要shared_ptr仍然存在,其所指向的对象也就仍然存在。

相关操作

weak_ptr的相关操作如下图所示:

`weak_ptr`的相关操作

参考资料

  1. C++ Primer - 12.1.6 weak_ptr

文章作者: alex Li
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 alex Li !
  目录