弱引用能指针 weak_ptr

弱引用智能指针

概述

弱引用智能指针std::weak_ptr可以看做是shared_ptr的助手,它不管理shared_ptr内部的指针。std::weak_ptr没有重载操作符*和->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视shared_ptr中管理的资源是否存在

基本用法

初始化
// 默认构造函数
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;

在C++11中,weak_ptr的初始化可以通过以上提供的构造函数来完成初始化,具体使用方法如下:

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

int main()
{
   
    shared_ptr<int> sp(new int);

    weak_ptr<int> wp1;
    weak_ptr<int> wp2(wp1);
    weak_ptr<int> wp3(sp);
    weak_ptr<int> wp4;
    wp4 = sp;
    weak_ptr<int> wp5;
    wp5 = wp3;

    return 0;
}
  • weak_ptr wp1;构造了一个空weak_ptr对象
  • weak_ptr wp2(wp1);通过一个空weak_ptr对象构造了另一个空weak_ptr对象
  • weak_ptr wp3(sp);通过一个shared_ptr对象构造了一个可用的weak_ptr实例对象
  • wp4 = sp;通过一个shared_ptr对象构造了一个可用的weak_ptr实例对象(这是一个隐式类型转换)
  • wp5 = wp3;通过一个weak_ptr对象构造了一个可用的weak_ptr实例对象
常用方法
1.use_count()

通过调用std::weak_ptr类提供的use_count()方法可以获得当前所观测资源的引用计数,函数原型如下:

// 函数返回所监测的资源的引用计数
long int use_count() const noexcept;

修改一下上面的测试程序,添加打印资源引用计数的代码:

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

int main() 
{
   
    shared_ptr<int> sp(new int);

    weak_ptr<int> wp1;
    weak_ptr<int> wp2(wp1);
    weak_ptr<int> wp3(sp);
    weak_ptr<int> wp4;
    wp4 = sp;
    weak_ptr<int> wp5;
    wp5 = wp3;

    cout << "use_count: " << endl;
    cout << "wp1: " << wp1.use_count() << endl;
    cout << "wp2: " << wp2.use_count() << endl;
    cout << "wp3: " << wp3.use_count() << endl;
    cout << "wp4: " << wp4.use_count() << endl;
    cout << "wp5: " << wp5.use_count() << endl;
    return 0;
}

测试程序输出的结果为:

use_count:
wp1: 0
wp2: 0
wp3: 1
wp4: 1
wp5: 1

通过打印的结果可以知道,虽然弱引用智能指针wp3、wp4、wp5监测的资源是同一个,但是它的引用计数并没有发生任何的变化,也进一步证明了weak_ptr只是监测资源,并不管理资源。

2.expired()

通过调用std::weak_ptr类提供的expired()方法来判断观测的资源是否已经被释放,函数原型如下:

// 返回true表示资源已经被释放, 返回false表示资源没有被释放
bool expired() const noexcept;

函数的使用方法如下:

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

int main() 
{
   
    shared_ptr<int> shared(new int(10));
    weak_ptr<int> weak(shared);
    cout << "1. weak " << (weak.expired() ? "is" : "is not") << " expired" << endl;

    shared.reset();
    cout << "2. weak " << (weak.expired() ? "is" : "is not") << " expired" << endl;

    return 0;
}

测试代码输出的结果:

1. weak is not expired
2. weak is expired

weak_ptr监测的就是shared_ptr管理的资源,当共享智能指针调用shared.reset();之后管理的资源被释放,因此weak.expired()函数的结果返回true,表示监测的资源已经不存在了。

3.lock()

通过调用std::weak_ptr类提供的lock()方法来获取管理所监测资源的shared_ptr对象,函数原型如下:

shared_ptr<element_type> lock() const noexcept;

函数的使用方法如下:

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

int main()
{
   
    shared_ptr<int> sp1, sp2;
    weak_ptr<int> wp;

    sp1 = std::make_shared<int>(520);
    wp = sp1;
    sp2 = wp.lock();
    cout << "use_count: " << wp.use_count() << endl;

    sp1.reset();
    cout << "use_count: " << wp.use_count() << endl;

    sp1 = wp.lock();
    cout << "use_count: " << wp.use_count() << endl;

    cout << "*sp1: " << *sp1 << endl;
    cout << "*sp2: " << *sp2 << endl;

    return 0;
}

测试代码输出的结果为:

use_count: 2
use_count: 1
use_count: 2
*sp1: 520
*sp2: 520
  • sp2 = wp.lock();通过调用lock()方法得到一个用于管理weak_ptr对象所监测的资源的共享智能指针对象,使用这个对象初始化sp2,此时所监测资源的引用计数为2
  • sp1.reset();共享智能指针sp1被重置,weak_ptr对象所监测的资源的引用计数减1
  • sp1 = wp.lock();sp1重新被初始化,并且管理的还是weak_ptr对象所监测的资源,因此引用计数加1
  • 共享智能指针对象sp1和sp2管理的是同一块内存,因此最终打印的内存中的结果是相同的,都是520
4.reset()

通过调用std::weak_ptr类提供的reset()方法来清空对象,使其不监测任何资源,函数原型如下:

void reset() noexcept;

函数的使用是非常简单的,示例代码如下:

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

int main() 
{
   
    shared_ptr<int> sp(new int(10));
    weak_ptr<int> wp(sp);
    cout << "1. wp " << (wp.expired() ? "is" : "is not") << " expired" << endl;

    wp.reset();
    cout << "2. wp " << (wp.expired() ? "is" : "is not") << " expired" << endl;

    return 0;
}

测试代码输出的结果为:

1. wp is not expired
2. wp is expired

weak_ptr对象sp被重置之后wp.reset();变成了空对象,不再监测任何资源,因此wp.expired()返回true

返回管理this的shared_ptr

如果在一个类中编写了一个函数,通过这个得到管理当前对象的共享智能指针,我们可能会写出如下代码:

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

struct Test
{
   
    shared_ptr<Test> getSharedPtr()
    {
   
        return shared_ptr<Test>(this);
    }

    ~Test()
    {
   
        cout << "class Test is disstruct ..." << endl;
    }

};

int main()
{
   
    shared_ptr<Test> sp1(new Test);
    cout << "use_count: " << sp1.use_count() << endl;
    shared_ptr<Test> sp2 = sp1->getSharedPtr();
    cout << "use_count: " << sp1.use_count() << endl;
    return 0;
}

执行上面的测试代码,运行中会出现异常,在终端还是能看到对应的日志输出:

use_count: 1
use_count: 1
class Test is disstruct ...
class Test is disstruct ...

通过输出的结果可以看到一个对象被析构了两次,其原因是这样的:在这个例子中使用同一个指针this构造了两个智能指针对象sp1和sp2,这二者之间是没有任何关系的,因为sp2并不是通过sp1初始化得到的实例对象。在离开作用域之后this将被构造的两个智能指针各自析构,导致重复析构的错误。

这个问题可以通过weak_ptr来解决,通过wek_ptr返回管理this资源的共享智能指针对象shared_ptr。C++11中为我们提供了一个模板类叫做std::enable_shared_from_this,这个类中有一个方法叫做shared_from_this(),通过这个方法可以返回一个共享智能指针,在函数的内部就是使用weak_ptr来监测this对象,并通过调用weak_ptr的lock()方法返回一个shared_ptr对象

修改之后的代码为:

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

struct Test : public enable_shared_from_this<Test>
{
   
    shared_ptr<Test> getSharedPtr()
    {
   
        return shared_from_this();
    }
    ~Test()
    {
   
        cout << "class Test is disstruct ..." << endl;
    }
};

int main()
{
   
    shared_ptr<Test> sp1(new Test);
    cout << "use_count: " << sp1.use_count() << endl;
    shared_ptr<Test> sp2 = sp1->getSharedPtr();
    cout << "use_count: " << sp1.use_count() << endl;
    return 0;
}

测试代码输出的结果为:

use_count: 1
use_count: 2
class Test is disstruct ...

最后需要强调一个细节:在调用enable_shared_from_this类的shared_from_this()方法之前,必须要先初始化函数内部weak_ptr对象,否则该函数无法返回一个有效的shared_ptr对象(具体处理方法可以参考上面的示例代码)。

看一下enable_shared_from_this这个类的简化版本:

#include <memory>

template <typename T>
class enable_shared_from_this {
   
private:
    mutable std::weak_ptr<T> weakThis;

protected:
    enable_shared_from_this() {
   }

    enable_shared_from_this(const enable_shared_from_this&) {
   }

    enable_shared_from_this& operator=(const enable_shared_from_this&) {
   
        return *this;
    }

    ~enable_shared_from_this() {
   }

public:
    std::shared_ptr<T> shared_from_this() {
   
        // 尝试获取弱引用计数对应的 shared_ptr
        std::shared_ptr<T> shared = weakThis.lock();
        if (!shared) {
   
            // 如果弱引用计数对应的 shared_ptr 不存在,创建一个新的 shared_ptr
            shared = std::shared_ptr<T>(this);
            // 更新 weakThis,指向当前对象
            weakThis = shared;
        }
        return shared;
    }
};

在这个简化版本的实现中,std::enable_shared_from_this 提供了一个 shared_from_this 成员函数,它首先尝试通过 weakThis.lock() 获取弱引用计数对应的 std::shared_ptr。如果该 std::shared_ptr 不存在(即弱引用计数为零),则创建一个新的 std::shared_ptr,然后将 weakThis 更新为指向当前对象。

需要注意的是,为了确保 std::shared_ptr 的正确使用,std::enable_shared_from_this 的构造函数是私有的,其子类的构造函数应当使用 std::make_shared 来创建对象,以确保控制块正确地分配和管理。此外,std::enable_shared_from_this 不应当被多重继承。

应用场景

解决循环引用问题

智能指针如果循环引用会导致内存泄露,比如下面的例子:

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

struct TA;
struct TB;

struct TA
{
   
private:
    shared_ptr<TB> bptr;
public:
    void setB(shared_ptr<TB> b) {
   
        bptr = b;
    }
    ~TA()
    {
   
        cout << "class TA is disstruct ..." << endl;
    }
};

struct TB
{
   
private:
    shared_ptr<TA> aptr;
public:
    void setA(shared_ptr<TA> a) {
   
        aptr = a;
    }
    ~TB()
    {
   
        cout << "class TB is disstruct ..." << endl;
    }
};

void testPtr()
{
   
    shared_ptr<TA> ap(new TA);
    shared_ptr<TB> bp(new TB);
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;

    ap->setB(bp);
    bp->setA(ap);
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;
}

int main()
{
   
    testPtr();
    return 0;
}

测试程序输出的结果如下:

TA object use_count: 1
TB object use_count: 1
TA object use_count: 2
TB object use_count: 2

在测试程序中,共享智能指针ap、bp对TA、TB实例对象的引用计数变为2,在共享智能指针离开作用域之后引用计数只能减为1这种情况下不会去删除智能指针管理的内存,导致类TA、TB的实例对象不能被析构,最终造成内存泄露。通过使用weak_ptr可以解决这个问题,只要将类TA或者TB的任意一个成员改为weak_ptr,修改之后的代码如下:

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

struct TA;
struct TB;

struct TA
{
   
private:
    shared_ptr<TB> bptr;
public:
    void setB(shared_ptr<TB> b) {
   
        bptr = b;
    }
    ~TA()
    {
   
        cout << "class TA is disstruct ..." << endl;
    }
};

struct TB
{
   
private:
    weak_ptr<TA> aptr;
public:
    void setA(weak_ptr<TA> a) {
   
        aptr = a;
    }
    ~TB()
    {
   
        cout << "class TB is disstruct ..." << endl;
    }
};

void testPtr()
{
   
    shared_ptr<TA> ap(new TA);
    shared_ptr<TB> bp(new TB);
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;

    ap->setB(bp);
    bp->setA(ap);
    cout << "TA object use_count: " << ap.use_count() << endl; // 1
    cout << "TB object use_count: " << bp.use_count() << endl; // 2
}

int main()
{
   
    testPtr();
    return 0;
}

程序输出的结果:

TA object use_count: 1
TB object use_count: 1
TA object use_count: 2
TB object use_count: 1
class TB is disstruct ...
class TA is disstruct ...

在最开始初始化ap和bp的时候, 这两个共享指针的引用计数都为1, 在执行两行set语句后, bp是弱引用指针, 所以ap的引用计数不变, 而bp引用计数+1

离开作用域后, ap的引用计数减为0, 类TA的对象被析构

在类TA的实例对象被析构的时候,内部的bptr也被析构,其对TB对象的管理解除,内存的引用计数减为1,当共享智能指针bp离开作用域之后,对TB对象的管理也解除了,内存的引用计数减为0,类TB的实例对象被析构。

相关推荐

  1. 引用指针 weak_ptr

    2023-12-05 21:44:03       34 阅读
  2. 引用、软引用引用和虚引用

    2023-12-05 21:44:03       38 阅读

最近更新

  1. TCP协议是安全的吗?

    2023-12-05 21:44:03       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-05 21:44:03       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-05 21:44:03       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-05 21:44:03       18 阅读

热门阅读

  1. 第八章 django

    2023-12-05 21:44:03       37 阅读
  2. 程序员必备注释模板---佛祖保佑

    2023-12-05 21:44:03       41 阅读
  3. 配置服务器免密登录

    2023-12-05 21:44:03       38 阅读
  4. 计算机网络入门

    2023-12-05 21:44:03       33 阅读
  5. FastDFS部署

    2023-12-05 21:44:03       43 阅读
  6. 自然语言处理:生成式模型简介

    2023-12-05 21:44:03       41 阅读