zoukankan      html  css  js  c++  java
  • 智能指针之 weak_ptr

    1. weak_ptr 介绍

    std::weak_ptr 是一种智能指针,它对被 std::shared_ptr 管理的对象存在非拥有性("弱")引用。在访问所引用的对象指针前必须先转换为 std::shared_ptr。 主要用来表示临时所有权,当某个对象存在时才需要被访问。转换为shared_ptr的过程等于对象的shared_ptr 的引用计数加一,因此如果你使用weak_ptr获得所有权的过程中,原来的shared_ptr被销毁,则该对象的生命期会被延长至这个临时的 std::shared_ptr 被销毁为止。 weak_ptr还可以避免 std::shared_ptr 的循环引用

    std::weak_ptr简单使用:(编译系统:Linux centos 7.0 x86_64 编译器:gcc 4.8.5 )

    #include <memory>
    #include <iostream>
    
    class foo
    {
    public:
        foo()
        {
            std::cout << "foo construct.." << std::endl;
        }
    
        void method()
        {
            std::cout << "welcome Test foo.." << std::endl;
        }
    
        ~foo()
        {
            std::cout << "foo destruct.." << std::endl;
        }
    };
    
    int main()
    {
        // weak_ptr
        foo* foo2 = new foo();
    
        // share_ptr 管理对象
        std::shared_ptr<foo> shptr_foo2(foo2);
    
        // weak_ptr 弱引用
        std::weak_ptr<foo> weak_foo2(shptr_foo2);
    
        // 如果要获取数据指针,需要通过lock接口获取
        weak_foo2.lock()->method();
    
        std::shared_ptr<foo> tmp =  weak_foo2.lock();
    
        // 我们这边有尝试多次获取所有权(lock),看一下引用计数个数
        std::cout << "shptr_foo2 RefCount: " << weak_foo2.lock().use_count() << std::endl;
    
        return 0;
    }
    
    

    执行结果:

    ``` bash-4.2$ ./share_ptr foo construct.. welcome Test foo.. shptr_foo2 RefCount: 3 foo destruct..
    
    <p style="font-size: 15px; text-indent:2em; letter-spacing:1px; font-family: '微软雅黑';">我们可以看到,weak_ptr多次通过lock转换成shared_ptr,获得shared_ptr后可以成功调用管理对象的方法,这个过程中<font color="#ff0000">引用计数是在增加的,因此如果原来的shared_ptr销毁是不影响你这个临时对象使用, 资源析构正常</font>。  下面是gnu STL 库中最后调用lock函数会跑到的地方,引用计数 + 1 。(GNU STL 文件:shared_ptr_base.h) </p>
    
    ![](https://img2018.cnblogs.com/blog/1285081/201809/1285081-20180929233137997-1131170390.png)
    
    <p style="color: #AD5D0F;font-weight: bold;font-size: 20px; font-family: '微软雅黑';">2.  weak_ptr 实现和循环引用问题</p>
    ------
    
    <p style="font-size: 15px;  letter-spacing:1px; font-weight: bold; font-family: '微软雅黑';">1. shared_ptr 循环引用问题</p>
    
    <p style="font-size: 15px; text-indent:2em; letter-spacing:1px; font-family: '微软雅黑';">我们首先看一下循环引用的问题,具体代码如下: </p>
    
    测试类:
    

    include

    include

    class foo;
    class Test
    {
    public:
    Test()
    {
    std::cout << "construct.." << std::endl;
    }

    void method()
    {
        std::cout << "welcome Test.." << std::endl;
    }
    
    ~Test()
    {
        std::cout << "destruct.." << std::endl;
    }
    

    public:
    std::shared_ptr fooptr;
    };

    class foo
    {
    public:
    foo()
    {
    std::cout << "foo construct.." << std::endl;
    }

    void method()
    {
        std::cout << "welcome Test foo.." << std::endl;
    }
    
    ~foo()
    {
        std::cout << "foo destruct.." << std::endl;
    }
    

    public:
    std::shared_ptr testptr;
    };

    main函数:
    

    int main()
    {
    // 循环引用 测试
    Test* t2 = new Test();
    foo* foo1 = new foo();

    std::shared_ptr<Test> shptr_Test(t2);
    std::shared_ptr<foo>  shptr_foo(foo1);
    
    std::cout << "shptr_Test RefCount: " << shptr_Test.use_count() << std::endl;
    std::cout << "shptr_foo RefCount: " << shptr_foo.use_count() << std::endl;
    
    shptr_Test->fooptr = shptr_foo;
    shptr_foo->testptr = shptr_Test;
      
    std::cout << "shptr_Test RefCount: " << shptr_Test.use_count() << std::endl;
    std::cout << "shptr_foo RefCount: " << shptr_foo.use_count() << std::endl;
    
    return 0;
    

    }

    测试结果:
    

    bash-4.2$ ./share_ptr
    construct..
    foo construct..
    shptr_Test RefCount: 1
    shptr_foo RefCount: 1
    shptr_Test RefCount: 2
    shptr_foo RefCount: 2

    <p style="font-size: 15px; text-indent:2em; letter-spacing:1px; font-family: '微软雅黑';">从打印结果可以很明显的看出,经过循环引用, 对象引用计数变成了2,并且执行完后,<font color="#ff0000">资源没有释放,没有调用类的destruct(析构)函数</font>。</p>
    
    <p style="font-size: 15px; text-indent:2em; letter-spacing:1px; font-family: '微软雅黑';">将类中的std::shared_ptr<foo> 换成 std::weak_ptr<foo>:</p>
    

    class foo;
    class Test
    {
    public:
    Test()
    {
    std::cout << "construct.." << std::endl;
    }

    void method()
    {
        std::cout << "welcome Test.." << std::endl;
    }
    
    ~Test()
    {
        std::cout << "destruct.." << std::endl;
    }
    

    public:
    std::weak_ptr fooptr;
    };

    class foo
    {
    public:
    foo()
    {
    std::cout << "foo construct.." << std::endl;
    }

    void method()
    {
        std::cout << "welcome Test foo.." << std::endl;
    }
    
    ~foo()
    {
        std::cout << "foo destruct.." << std::endl;
    }
    

    public:
    std::weak_ptr testptr;
    };

    
    <p style="font-size: 15px; text-indent:2em; letter-spacing:1px; font-family: '微软雅黑';">再看下weak_ptr的执行结果,可以看到<font color="#ff0000">计数正常,资源成功释放</font>:</p>
    

    bash-4.2$ ./share_ptr
    construct..
    foo construct..
    shptr_Test RefCount: 1
    shptr_foo RefCount: 1
    shptr_Test RefCount: 1
    shptr_foo RefCount: 1
    foo destruct..
    destruct..

    <p style="font-size: 15px;  letter-spacing:1px; font-weight: bold; font-family: '微软雅黑';">2. weak_ptr 实现</p>
    
    <p style="font-size: 15px; text-indent:2em; letter-spacing:1px; font-family: '微软雅黑';">我们这边贴一下weak_ptr类的代码:</p>
    

    template
    class weak_ptr
    {
    public:
    template
    friend class weak_ptr;

    template <class S>
    friend class shared_ptr;
    
    constexpr weak_ptr() noexcept : m_iWeakRefCount(nullptr), m_ptr(nullptr) { }
    
    weak_ptr( const weak_ptr<T>& rhs ) noexcept : m_iWeakRefCount(rhs.m_iWeakRefCount)
    {
        m_ptr = rhs.lock().getPointer();
    }
    
    weak_ptr( const shared_ptr<T>& rhs ) noexcept
     : m_iWeakRefCount(rhs.m_iRefCount), m_ptr(rhs.m_ptr) { }
    
    template <typename S>
    weak_ptr & operator=(const shared_ptr<S> & rhs)
    {
        m_ptr = dynamic_cast<T *>(rhs.m_ptr);
        m_iWeakRefCount = rhs.m_iRefCount;
        return *this;
    }
    
    template <typename S>
    weak_ptr & operator=(const weak_ptr<S> & rhs)
    {
        m_ptr = dynamic_cast<T *>(rhs.m_ptr);
        m_iWeakRefCount = rhs.m_iWeakRefCount;
        return *this;
    }
    
    weak_ptr& operator=( const weak_ptr& rhs ) noexcept
    {
        m_iWeakRefCount = rhs.m_iWeakRefCount;
        m_ptr = rhs.m_ptr;
    
        return *this;
    }
    
    weak_ptr& operator=( const shared_ptr<T>& rhs ) noexcept
    {
        m_iWeakRefCount = rhs.m_iRefCount;
        m_ptr = rhs.m_ptr;
    
        return *this;
    }
    
    shared_ptr<T> lock() const noexcept
    {
        shared_ptr<T> tmp;
        if(m_iWeakRefCount && *m_iWeakRefCount > 0)
        {
            tmp.m_iRefCount = m_iWeakRefCount;
            tmp.m_ptr = m_ptr;
    
            if(tmp.m_iRefCount)
            {
                ++(*tmp.m_iRefCount);
            }
        }
    
        return tmp;
    }
    
    int use_count()
    {
        return *m_iWeakRefCount;
    }
    
    bool expired() const noexcept
    { 
        return *m_iWeakRefCount == 0;
    }
    
    void Reset()
    {
        m_ptr = NULL;
        m_iWeakRefCount = NULL;
    }
    

    private:
    int * m_iWeakRefCount;

    T* m_ptr;
    

    };

    <p style="font-size: 15px; text-indent:2em; letter-spacing:1px; font-family: '微软雅黑';">主要注意的是lock函数,如果计数指针为空,那么会返回一个空的shared_ptr,然后就是<font color="#ff0000">不能重载operator*和operator-> 操作符</font>。</p>
    
    主要参考: <a href="https://zh.cppreference.com/w/cpp/memory/weak_ptr" target="_red"><font color=#00ffff size=5>cppreference.com</font></a>
    完整实现见:<a href="https://github.com/Yejy813/stl_implement/tree/master/smart_ptr" target="_red"><font color=#00ffff size=5>smart_ptr</font></a>
    
    <p style="font-size: 15px;  letter-spacing:1px; font-weight: bold; font-family: '微软雅黑';">3. enable_shared_from_this</p>
    
    <p style="font-size: 15px; text-indent:2em; letter-spacing:1px; font-family: '微软雅黑';">这边还有一个点也要介绍一下,那就是enable_shared_from_this,这个主要是为了处理<font color="#ff0000">在shared_ptr管理的对象中要使用该对象的指针所引出的问题</font>。 我们看下下面这个例子:  </p>
    

    class foo
    {
    public:
    std::shared_ptr getptr()
    {
    // 如果类中要返回自己的指针怎么办?
    return std::shared_ptr(this);
    }

    ~foo() 
    { 
        std::cout << "foo destruct .. " << std::endl; 
    }
    

    };

    int main()
    {
    std::shared_ptr bp1(new foo());
    bp1->getptr();
    std::cout << "bp1.use_count() = " << bp1.use_count() << std::endl;
    }

    <p style="font-size: 15px; text-indent:2em; letter-spacing:1px; font-family: '微软雅黑';"><font color="#ff0000">看下结果,释放两次</font>:</p>
    

    ash-4.2$ ./share_ptr
    foo destruct ..
    bp1.use_count() = 1
    foo destruct ..

    <p style="font-size: 15px; text-indent:2em; letter-spacing:1px; font-family: '微软雅黑';">其实我们都不用测试,因为你如果<font color="#ff0000">直接使用该对象的this指针又拷贝给另一个shared_ptr,那不就等于两个没有关系的shared_ptr管理同一个对象了吗? 释放的时候等于会调用两次该对象的析构函数</font>。enable_shared_from_this就是用来解决这个问题的。看下代码:</p>
    

    class foo : public std::enable_shared_from_this
    {
    public:
    std::shared_ptr getptr()
    {
    return shared_from_this();
    }

    ~foo() 
    { 
        std::cout << "foo destruct .. " << std::endl; 
    }
    

    };

    int main()
    {
    std::shared_ptr bp1(new foo());
    bp1->getptr();
    std::cout << "bp1.use_count() = " << bp1.use_count() << std::endl;
    }

    <p style="font-size: 15px; text-indent:2em; letter-spacing:1px; font-family: '微软雅黑';">看下结果,成功释放:</p>
    

    bash-4.2$ ./share_ptr
    bp1.use_count() = 1
    foo destruct ..

    
    <p style="font-size: 15px; text-indent:2em; letter-spacing:1px; font-weight: bold; font-family: '微软雅黑';"><font color="#ff0000">总结一下,weak_ptr本质是以一种观察者的形象存在,它可以获取到观察主体的状态,但是无法获取直接获取到观察主体,无法直接对观察主体修改,无法释放观察主体的资源,你只能通过转换成shared_ptr来做一些事情。 和观察者模式很像,订阅,得到观察主体状态,在多线程环境下会比较管用! </font></p>
    
    <p style="font-size: 15px;text-indent:60em;letter-spacing:1px; font-family: '微软雅黑';">2018年9月30日00:40:02</p>
  • 相关阅读:
    QFramework 使用指南 2020(二):下载与版本介绍
    QFramework 使用指南 2020 (一): 概述
    Unity 游戏框架搭建 2018 (二) 单例的模板与最佳实践
    Unity 游戏框架搭建 2018 (一) 架构、框架与 QFramework 简介
    Unity 游戏框架搭建 2017 (二十三) 重构小工具 Platform
    Unity 游戏框架搭建 2017 (二十二) 简易引用计数器
    Unity 游戏框架搭建 2017 (二十一) 使用对象池时的一些细节
    你确定你会写 Dockerfile 吗?
    小白学 Python 爬虫(8):网页基础
    老司机大型车祸现场
  • 原文地址:https://www.cnblogs.com/blog-yejy/p/9727070.html
Copyright © 2011-2022 走看看