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>
  • 相关阅读:
    Java 学习 第二篇;面向对象 定义类的简单语法:
    Java 学习 第一篇
    Compiler Principles 语法分析
    未来智能机的发展趋势
    C语言IO操作总结
    python学习资料
    JavaScript 数组的创建
    JavaScript中的逗号运算符
    JavaScript本地对象 内置对象 宿主对象
    JavaScript的this用法
  • 原文地址:https://www.cnblogs.com/blog-yejy/p/9727070.html
Copyright © 2011-2022 走看看