弱引用智能指针

发布时间 2023-12-12 17:03:54作者: Beasts777

文章参考:

爱编程的大丙 (subingwen.cn)

1. 概述

弱引用智能指针std::weak_ptr是共享智能指针std::shared_ptr的助手,它不管理shared_ptr内部的原始指针,也没有重载操作符*->,因此不共享指针,不能操作资源,所以它的构造和析构都不会影响引用计数。其存在的意义就是监视shared_ptr中管理的资源是否存在。

1.1 初始化

弱引用指针有构造函数如下:

  • 默认构造函数:

    constexpr weak_ptr() noexcept;
    
  • 拷贝构造函数:

    weak_ptr (const weak_ptr& x) noexcept;
    template <class U>
    weak_ptr (const weak_ptr<U>& x) noexcept;
    
  • 通过shared_ptr对象构造:

    template <class U>
    weak_ptr (const shared_ptr<U>& x) noexcept;
    

EG:

  • 代码:

    #include <iostream>
    #include <memory>
    using namespace std;
    
    int main(void){
        shared_ptr<int> sp(new int(100));
    	weak_ptr<int> wp1;
    	weak_ptr<int> wp2(wp1);
    	weak_ptr<int> wp3(sp);
    	wp1 = sp;   
        weak_ptr<int> wp4 = wp1;
        return 0;
    }               
    
  • 分析:

    • 第6行:使用默认构造函数,创建了一个空的弱引用智能指针。
    • 第7行:通过已经存在的弱引用指针,创建了一个新的弱引用指针。因为原本的弱引用指针为空,所以新创建的弱引用指针也为空。
    • 第8行:通过共享智能指针对象,创建了一个可用的弱引用智能指针对象,可以监管该共享智能指针对象。
    • 第9行:通过共享智能指针对象,创建了一个可用的弱引用智能指针对象。(实际上是一个隐式转换)
    • 第10行:通过一个弱引用智能指针对象创建一个可用的弱引用智能指针对象。(拷贝赋值函数)

1.2 常用成员方法

1.2.1 use_count()

原型:

long int use_count() const noexcept;

作用:

用于获取当前所观测资源的引用计数。

EG:

  • 代码:

    #include <iostream>
    #include <memory>
    using namespace std;
    
    int main(void){
        shared_ptr<int> sp(new int(100));
    	weak_ptr<int> wp1;
    	weak_ptr<int> wp2(wp1);
    	weak_ptr<int> wp3(sp);
    	wp1 = sp;   
        weak_ptr<int> wp4 = wp1;
        
        cout << "wp1.use_cout()==" << wp1.use_count() << endl;
        cout << "wp2.use_cout()==" << wp2.use_count() << endl;
        cout << "wp3.use_cout()==" << wp3.use_count() << endl;
        cout << "wp4.use_cout()==" << wp4.use_count() << endl;
        return 0;
    }               
    
  • 输出:

    wp1.use_cout()==1
    wp2.use_cout()==0
    wp3.use_cout()==1
    wp4.use_cout()==1
    
  • 结论:虽然wp1wp2wp3监视的是同一块资源,但它的引用计数不会发生变化。

1.2.3 expired()

原型:

bool expired() const noexcept;

作用:

用于判断监测的资源是否已经被释放。

EG:

  • 代码:

    #include <iostream>
    #include <memory>
    using namespace std;
    
    int main(void){
        shared_ptr<int> sp(new int(100));
    	weak_ptr<int> wp(sp);
        cout << "wp.expired()==" << wp.expired() << endl;                                                               
        sp.reset();
     	cout << "wp.expired()==" << wp.expired() << endl;
        return 0;
    }  
    
  • 输出:

    wp.expired()==0
    wp.expired()==1
    
  • 分析:弱引用指针检测的是共享智能指针sp管理的资源,sp通过reset()函数不再管理该资源,该资源的引用计数变为0,被析构了,因此wp.expired()从1变成了0。

1.2.3 lock()

原型:

shared_ptr<element_type> lock() const noexcept;

作用:

获取所监测资源的shared_ptr对象。

EG:

  • 代码:

    #include <iostream>
    #include <memory>
    using namespace std;
    
    int main(void){
        shared_ptr<int> sp1, sp2;
      	weak_ptr<int> wp;
     	sp1 = make_shared<int>(10);
        wp = sp1;
        sp2 = wp.lock();
        cout << "wp.use_count()==" << wp.use_count() << endl;
     	sp1.reset();
    	cout << "wp.use_count()==" << wp.use_count() << endl;
        sp1 = wp.lock();
        cout << "wp.use_count()==" << wp.use_count() << endl;
        return 0;
    }  
    
  • 输出:

    wp.use_count()==2
    wp.use_count()==1
    wp.use_count()==2
    
  • 分析:

    • 第10行:通过调用lock()方法,获取一个弱引用指针所监测资源的共享智能指针对象,使用该对象初始化sp2。此时检测资源的引用计数为2
    • 第12行:通过reset()函数重置共享智能指针sp1。此时检测资源的引用计数变为1
    • 第14行:通过调用lock()方法,获取一个弱引用指针所监测资源的共享智能指针对象,使用该对象初始化sp1。此时检测资源的引用计数变回2

1.2.4 reset()

原型:

void reset() noexcept;

作用:

重置std::weak_ptr,使其不监测任何资源。注意:只是不检测了,但原有的资源依旧存在,还可以使用共享智能指针调用

EG:

  • 代码:

    #include <iostream>
    #include <memory>
    using namespace std;
    
    int main(void){
        shared_ptr<int> sp = make_shared<int>(10);
      	weak_ptr<int> wp(sp);
        cout << "wp.expired()==" << wp.expired() << endl;
        cout << "wp.use_count()==" << wp.use_count() << endl;
     	wp.reset();
        cout << "wp.expired()==" << wp.expired() << endl;
    	cout << "wp.use_count()==" << wp.use_count() << endl;
        cout << "*sp==" << *sp << endl;
        return 0;
    }  
    
  • 输出:

    wp.expired()==0
    wp.use_count()==1
    wp.expired()==1
    wp.use_count()==0
    100
    
  • 分析:

    • 第11行:reset()方法让弱引用指针对象wp不再监视任何对象,此时使用use_count()只会返回0,使用expired()只会返回1
    • 第13行:虽然弱引用指针对象wpreset()重置,不再监测任何对象了,但共享智能指针对象sp依旧在工作。

2. 返回this的shared_ptr

2.1 问题

如果在类中编写一个函数,该函数的返回值是一个管理当前对象的共享指针,那么需要注意,如果使用该函数对另一个共享智能指针进行初始化,极易导致运行时错误。一个典型错误如下:

#include <iostream>
#include <memory> 
using namespace std;

class Test {
public:
    Test(){ cout << "constructor" << endl; }
    ~Test(){ cout << "destructor" << endl; }
    shared_ptr<Test> get_shared_ptr(){
        return shared_ptr<Test>(this);
    }
};

int main(void){
    shared_ptr<Test> sp1(new Test);
    shared_ptr<Test> sp2 = sp1->get_shared_ptr();
   	cout << "*sp1==" << *sp1 << endl;
    cout << "*sp2==" << *sp2 << endl;
    return 0;
}

运行时报错如下:

constructor
*sp1==0x560e7c236eb0
*sp2==0x560e7c236eb0
destructor
destructor
free(): double free detected in tcache 2
已放弃 (核心已转储)

可以看到:命名只创建了一次对象,该内存却被析构了两次。出现这种问题的根本原因在于:sp2实际上并不是通过sp1构造的,二者并虽然指向一块内存,但是各自的引用计数却都是1,而不是2。因此当sp1sp2声明周期结束时,会各自析构内存0x560e7c236eb0,导致一块内存被析构了两次。

2.2 方法

2.1中的问题可以通过weak_ptr解决。C++11为我们提供了一个模板类std::enabled_shared_from_this<T>,这个类中有一个方法叫做shared_from_this(),通过该方法可以返回一个共享智能指针。其内部实现逻辑为:使用weak_ptr来监测this对象,并通过调用weak_ptrlock()方法返回一个shared_ptr对象。

EG:

  • 代码:

    #include <iostream>
    #include <memory> 
    using namespace std;
    
    class Test: public enable_shared_from_this<Test> {
    public:
        Test(){ cout << "constructor" << endl; }
        ~Test(){ cout << "destructor" << endl; }
        shared_ptr<Test> get_shared_ptr(){
            return shared_from_this();
        }
    };
    
    int main(void){
        shared_ptr<Test> sp1(new Test);
        shared_ptr<Test> sp2 = sp1->get_shared_ptr();
       	cout << "*sp1==" << *sp1 << endl;
        cout << "*sp2==" << *sp2 << endl;
        return 0;
    }
    
  • 输出:

    constructor
    *sp1==0x560a2c3cbeb0
    *sp2==0x560a2c3cbeb0
    destructor
    

注意:在调用enable_shared_from_this类的shared_from_this()方法之前,必须要先初始化函数内部的weak_ptr对象,否则该函数无法返回一个有效的shared_ptr对象。这是因为shared_from_this()方法的内在逻辑就是通过弱引用指针监测this,然后调用lock()方法返回共享智能指针对象。

3. 循环引用问题

3.1 错误

智能指针如果存在循环引用,会导致内存泄漏。案例如下:

  • 代码:

    #include <iostream>
    #include <memory> 
    using namespace std;
    
    class A;
    class B;
    
    class A {
    public:
        shared_ptr<B> sp;
        A() { cout << "A constructor" << endl; }
        ~A() { cout << "A destructor" << endl; }
    };
    
    class B {
    public:
        shared_ptr<A> sp;
        B() { cout << "B constructor" << endl; }
        ~B() { cout << "B destructor" << endl; }
    };
    
    int main(void){
        shared_ptr<A> sp_a(new A());
        shared_ptr<B> sp_b(new B());
        cout << "sp_a.use_count()==" << sp_a.use_count() << endl;
        cout << "sp_b.use_count()==" << sp_b.use_count() << endl;
        // 循环引用
        sp_a->sp = sp_b;
        sp_b->sp = sp_a;
        cout << "sp_a.use_count()==" << sp_a.use_count() << endl;
        cout << "sp_b.use_count()==" << sp_b.use_count() << endl;
        return 0;
    }
    
  • 输出:

    A constructor
    B constructor
    sp_a.use_count()==1
    sp_b.use_count()==1
    sp_a.use_count()==2
    sp_b.use_count()==2
    
  • 分析:可以看到,共享智能指针指向的内存最后并没有被析构。这是因为程序中的共享智能指针形成了循环引用的问题,sp_a指向sp_a->sp,sp_a->sp指向sp_bsp_b指向sp_b->sp,sp_b->sp指向sp_a,循环引用形成,导致sp_asp_b的引用计数发生错误,当共享智能指针离开作用域时引用计数只能减到1,因此无法释放目标内存。

3.2 方法

通过将A类或B类的智能引用成员修改为弱引用智能指针即,打破循环引用链条即可。

  • 代码:

    #include <iostream>
    #include <memory> 
    using namespace std;
    
    class A;
    class B;
    
    class A {
    public:
        weak_ptr<B> sp;			// 修改为弱引用指针
        A() { cout << "A constructor" << endl; }
        ~A() { cout << "A destructor" << endl; }
    };
    
    class B {
    public:
        shared_ptr<A> sp;
        B() { cout << "B constructor" << endl; }
        ~B() { cout << "B destructor" << endl; }
    };
    
    int main(void){
        shared_ptr<A> sp_a(new A());
        shared_ptr<B> sp_b(new B());
        cout << "sp_a.use_count()==" << sp_a.use_count() << endl;
        cout << "sp_b.use_count()==" << sp_b.use_count() << endl;
        // 循环引用
        sp_a->sp = sp_b;
        sp_b->sp = sp_a;
        cout << "sp_a.use_count()==" << sp_a.use_count() << endl;
        cout << "sp_b.use_count()==" << sp_b.use_count() << endl;
        return 0;
    }
    
  • 输出:

    A constructor
    B constructor
    sp_a.use_count()==1
    sp_b.use_count()==1
    sp_a.use_count()==2
    sp_b.use_count()==1
    B destructor
    A destructor