您的位置:首页 > 编程语言 > C语言/C++

C++ 智能指针weak_ptr用途浅析

2020-01-15 00:48 120 查看

文章目录

  • 3. 总结
  • C++ 智能指针weak_ptr用途浅析

    我们知道C++智能指针有

    shared_ptr
    unique_ptr
    ,这两种指针基本就可以胜任堆内存的管理了,那么C++为什么还要提出
    weak_ptr
    呢?

    weak_ptr
    这个东西到底有什么用途呢?

    1. weak_ptr的特性

    weak_ptr
    也是指向
    shared_ptr
    指向的对象,但是并不管理引用计数和内存,操作如下:

    所以,如果要使用weak_ptr,必须锁定为shared_ptr

    shared_ptr<element_type> lock() const noexcept;

    从上面我们也可以知道,weak_ptr的初始化也是从shared_ptr来的,如下:

    //default (1)
    constexpr weak_ptr() noexcept;
    
    //copy (2)
    weak_ptr (const weak_ptr& x) noexcept;
    template <class U> weak_ptr (const weak_ptr<U>& x) noexcept;
    
    //from shared_ptr (3)
    template <class U> weak_ptr (const shared_ptr<U>& x) noexcept;

    从这里来看,

    weak_ptr
    完全就是一个
    shared_ptr
    鸡肋功能啊,一点用的没有。

    真是这样的吗?我们看一下下面分析。

    2. 二叉树的实现

    2.1 问题

    这里我们实现一个简单的二叉树,就三个节点,根,左子树,右子树(二叉树是一种非常查用的数据结构,这里使用这个例子,说明在我们实际情况中,这种场景其实非常多)。

    struct Node
    {
    std::shared_ptr<Node> Parent;
    std::shared_ptr<Node> LeftChild;
    std::shared_ptr<Node> RightChild;
    int Data;
    //
    Node(int d) : Data(d) {}
    ~Node()
    {
    std::cout << "~Node() called" << std::endl;
    }
    };
    
    void Tree()
    {
    std::shared_ptr<Node> Root = std::make_shared<Node>(200);
    std::shared_ptr<Node> Left = std::make_shared<Node>(100);
    std::shared_ptr<Node> Right = std::make_shared<Node>(300);
    
    Root->LeftChild = Left;
    Root->RightChild = Right;
    
    Left->Parent = Root;
    Right->Parent = Root;
    }

    如果我们运行这段代码可以发现,没有任何输出,析构函数并没有被调用?内存就这样被泄露了?我们来分析一下这段代码:

    1. std::shared_ptr<Node> Root = std::make_shared<Node>(200);
      : Root的引用计数为1.
    2. std::shared_ptr<Node> Left = std::make_shared<Node>(100);
      : Left引用计数为1.
    3. std::shared_ptr<Node> Right = std::make_shared<Node>(300);
      : Right 引用计数为1.
    4. Root->LeftChild = Left;
      : Left引用计数为2.
    5. Root->RightChild = Right;
      : Right引用计数为2.
    6. Left->Parent = Root;
      : Root引用计数为2.
    7. Right->Parent = Root;
      : Root引用计数为3.

    离开作用域的时候:

    1. Root被释放,引用计数减1 为2.
    2. Left被释放,引用计数为1.
    3. Right被释放,引用计数为1.

    也就是说,所有的引用计数都不为0,没有任何对象被释放;这个也就是典型的环形引用;也就是说,shared_ptr在环形引用中会导致引用计数循环使用

    解决办法两种。

    2.2 方案1

    void Tree()
    {
    std::shared_ptr<Node> Root = std::make_shared<Node>(200);
    std::shared_ptr<Node> Left = std::make_shared<Node>(100);
    std::shared_ptr<Node> Right = std::make_shared<Node>(300);
    
    Root->LeftChild = Left;
    Root->RightChild = Right;
    
    Left->Parent = Root;
    Right->Parent = Root;
    
    Root->LeftChild.reset();
    Root->RightChild.reset();
    
    Left->Parent.reset();
    Right->Parent.reset();
    }

    此时执行结果:

    ~Node() called
    ~Node() called
    ~Node() called

    这种办法很明确,Root快要释放的时候,左右的清理,同样左右子树也一样,不过这种方法也太扯淡了,下面提供另外一种办法。

    2.3 方案2

    我们可以这么理解,对于一个节点来说,只要不是根,那么父节点应该是存在的,子节点就不一定,所以我们可以将子节点声明为弱引用:

    struct Node
    {
    std::shared_ptr<Node> Parent;
    std::weak_ptr<Node> LeftChild;
    std::weak_ptr<Node> RightChild;
    int Data;
    
    //
    Node(int d) : Data(d) {}
    ~Node()
    {
    std::cout << "~Node() called" << std::endl;
    }
    };
    
    void Tree()
    {
    std::shared_ptr<Node> Root = std::make_shared<Node>(200);
    std::shared_ptr<Node> Left = std::make_shared<Node>(100);
    std::shared_ptr<Node> Right = std::make_shared<Node>(300);
    
    Root->LeftChild = Left;
    Root->RightChild = Right;
    
    Left->Parent = Root;
    Right->Parent = Root;
    }

    此时执行结果:

    ~Node() called
    ~Node() called
    ~Node() called

    至于引用计数,大家可以自己算一算。当然这里,也可以直接将parent作为弱引用,理解不同随意设置都可以。

    3. 总结

    当然上面树的例子只是一个说明,真实场景可能有很多会有类型情况。总之

    shared_ptr
    在环形引用中,带来了循环引用的弊端,所以,需要将其中一个设置为弱引用(根据实际情况确定,一般将管理者使用
    shared_ptr
    ,其他引用管理者的使用
    weak_ptr
    ,这么看刚刚的树的
    weak_ptr
    属性刚好设置反了)。


    因为循环引用,析构的时候只能释放一次引用计数;而实际的引用计数为2.

    • 点赞 5
    • 收藏
    • 分享
    • 文章举报
    xiangbaohui 发布了28 篇原创文章 · 获赞 18 · 访问量 1054 私信 关注
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: