欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

智能指针之 weak_ptr

程序员文章站 2022-06-20 09:38:43
1. weak_ptr 介绍 std::weak_ptr 是一种智能指针,它对被 std::shared_ptr 管理的对象存在非拥有性("弱")引用。在访问所引用的对象指针前必须先转换为 std::shared_ptr。 主要用来表示临时所有权,当某个对象存在时才需要被访问。转换为shared_p ......

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..

我们可以看到,weak_ptr多次通过lock转换成shared_ptr,获得shared_ptr后可以成功调用管理对象的方法,这个过程中引用计数是在增加的,因此如果原来的shared_ptr销毁是不影响你这个临时对象使用, 资源析构正常。 下面是gnu stl 库中最后调用lock函数会跑到的地方,引用计数 + 1 。(gnu stl 文件:shared_ptr_base.h)

智能指针之 weak_ptr

2. weak_ptr 实现和循环引用问题

1. shared_ptr 循环引用问题

我们首先看一下循环引用的问题,具体代码如下:

测试类:

#include <memory>
#include <iostream>

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<foo> 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<test> 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

从打印结果可以很明显的看出,经过循环引用, 对象引用计数变成了2,并且执行完后,资源没有释放,没有调用类的destruct(析构)函数

将类中的std::shared_ptr 换成 std::weak_ptr

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<foo> 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<test> testptr;
};

再看下weak_ptr的执行结果,可以看到计数正常,资源成功释放

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..

2. weak_ptr 实现

我们这边贴一下weak_ptr类的代码:

template <class t>
class weak_ptr
{
public:
    template <class s>
    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;
};

主要注意的是lock函数,如果计数指针为空,那么会返回一个空的shared_ptr,然后就是不能重载operator*和operator-> 操作符

主要参考:
完整实现见:smart_ptr

3. enable_shared_from_this

这边还有一个点也要介绍一下,那就是enable_shared_from_this,这个主要是为了处理在shared_ptr管理的对象中要使用该对象的指针所引出的问题。 我们看下下面这个例子:

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

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

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

看下结果,释放两次

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

其实我们都不用测试,因为你如果直接使用该对象的this指针又拷贝给另一个shared_ptr,那不就等于两个没有关系的shared_ptr管理同一个对象了吗? 释放的时候等于会调用两次该对象的析构函数。enable_shared_from_this就是用来解决这个问题的。看下代码:

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

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

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

看下结果,成功释放:

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

总结一下,weak_ptr本质是以一种观察者的形象存在,它可以获取到观察主体的状态,但是无法获取直接获取到观察主体,无法直接对观察主体修改,无法释放观察主体的资源,你只能通过转换成shared_ptr来做一些事情。 和观察者模式很像,订阅,得到观察主体状态,在多线程环境下会比较管用!

2018年9月30日00:40:02