智能指针(三):weak_ptr浅析

前言

weak_ptr这个指针天生一副“小弟”的模样,也是在C++11的时候引入的标准库,它的出现完全是为了弥补它老大shared_ptr天生有缺陷的问题,其实相比于上一代的智能指针auto_ptr来说,新进老大shared_ptr可以说近乎完美,但是通过引用计数实现的它,虽然解决了指针独占的问题,但也引来了引用成环的问题,这种问题靠它自己是没办法解决的,所以在C++11的时候将shared_ptrweak_ptr一起引入了标准库,用来解决循环引用的问题。

weak_ptr本身也是一个模板类,但是不能直接用它来定义一个智能指针的对象,只能配合shared_ptr来使用,可以将shared_ptr的对象赋值给weak_ptr,并且这样并不会改变引用计数的值。查看weak_ptr的代码时发现,它主要有lockswapresetexpiredoperator=use_count几个函数,与shared_ptr相比多了lockexpired函数,但是却少了get函数,甚至连operator*operator->都没有,可用的函数数量少的可怜,下面通过一些例子来了解一下weak_ptr的具体用法。

使用环境

  1. VS2015 + Windows7(应该是C++11标准)
  2. 头文件#include <memory>
  3. 命名空间using namespace std;

测试过程

  1. weak_ptr解决shared_ptr循环引用的问题
    定义两个类,每个类中又包含一个指向对方类型的智能指针作为成员变量,然后创建对象,设置完成后查看引用计数后退出,看一下测试结果:
class CB;
class CA
{
public:
    CA() { cout << "CA() called! " << endl; }
    ~CA() { cout << "~CA() called! " << endl; }
    void set_ptr(shared_ptr<CB>& ptr) { m_ptr_b = ptr; }
    void b_use_count() { cout << "b use count : " << m_ptr_b.use_count() << endl; }
    void show() { cout << "this is class CA!" << endl; }
private:
    shared_ptr<CB> m_ptr_b;
};

class CB
{
public:
    CB() { cout << "CB() called! " << endl; }
    ~CB() { cout << "~CB() called! " << endl; }
    void set_ptr(shared_ptr<CA>& ptr) { m_ptr_a = ptr; }
    void a_use_count() { cout << "a use count : " << m_ptr_a.use_count() << endl; }
    void show() { cout << "this is class CB!" << endl; }
private:
    shared_ptr<CA> m_ptr_a;
};

void test_refer_to_each_other()
{
    shared_ptr<CA> ptr_a(new CA());
    shared_ptr<CB> ptr_b(new CB());

    cout << "a use count : " << ptr_a.use_count() << endl;
    cout << "b use count : " << ptr_b.use_count() << endl;

    ptr_a->set_ptr(ptr_b);
    ptr_b->set_ptr(ptr_a);

    cout << "a use count : " << ptr_a.use_count() << endl;
    cout << "b use count : " << ptr_b.use_count() << endl;
}

测试结果如下:

CA() called!
CB() called!
a use count : 1
b use count : 1
a use count : 2
b use count : 2

通过结果可以看到,最后CACB的对象并没有被析构,其中的引用效果如下图所示,起初定义完ptr_aptr_b时,只有①③两条引用,然后调用函数set_ptr后又增加了②④两条引用,当test_refer_to_each_other这个函数返回时,对象ptr_aptr_b被销毁,也就是①③两条引用会被断开,但是②④两条引用依然存在,每一个的引用计数都不为0,结果就导致其指向的内部对象无法析构,造成内存泄漏。
wk_shared_ptr
解决这种状况的办法就是将两个类中的一个成员变量改为weak_ptr对象,因为weak_ptr不会增加引用计数,使得引用形不成环,最后就可以正常的释放内部的对象,不会造成内存泄漏,比如将CB中的成员变量改为weak_ptr对象,代码如下:

class CB
{
public:
    CB() { cout << "CB() called! " << endl; }
    ~CB() { cout << "~CB() called! " << endl; }
    void set_ptr(shared_ptr<CA>& ptr) { m_ptr_a = ptr; }
    void a_use_count() { cout << "a use count : " << m_ptr_a.use_count() << endl; }
    void show() { cout << "this is class CB!" << endl; }
private:
    weak_ptr<CA> m_ptr_a;
};

测试结果如下:

CA() called!
CB() called!
a use count : 1
b use count : 1
a use count : 1
b use count : 2
~CA() called!
~CB() called!

通过这次结果可以看到,CACB的对象都被正常的析构了,引用关系如下图所示,流程与上一例子相似,但是不同的是④这条引用是通过weak_ptr建立的,并不会增加引用计数,也就是说CA的对象只有一个引用计数,而CB的对象只有2个引用计数,当test_refer_to_each_other这个函数返回时,对象ptr_aptr_b被销毁,也就是①③两条引用会被断开,此时CA对象的引用计数会减为0,对象被销毁,其内部的m_ptr_b成员变量也会被析构,导致CB对象的引用计数会减为0,对象被销毁,进而解决了引用成环的问题。
wk_weak_ptr
2. 测试weak_ptr对引用计数的影响
其实weak_ptr本身设计的很简单,就是为了辅助shared_ptr的,它本身不能直接定义指向原始指针的对象,只能指向shared_ptr对象,同时也不能将weak_ptr对象直接赋值给shared_ptr类型的变量,最重要的一点是赋值给它不会增加引用计数:

void test1()
{
    // 编译错误 // error C2665: “std::weak_ptr<CA>::weak_ptr”: 3 个重载中没有一个可以转换所有参数类型
    // weak_ptr<CA> ptr_1(new CA());

    shared_ptr<CA> ptr_1(new CA());

    cout << "ptr_1 use count : " << ptr_1.use_count() << endl; // 输出:ptr_1 use count : 1

    shared_ptr<CA> ptr_2 = ptr_1;

    cout << "ptr_1 use count : " << ptr_1.use_count() << endl; // 输出:ptr_1 use count : 2
    cout << "ptr_2 use count : " << ptr_2.use_count() << endl; // 输出:ptr_1 use count : 2

    weak_ptr<CA> wk_ptr = ptr_1;

    cout << "ptr_1 use count : " << ptr_1.use_count() << endl; // 输出:ptr_1 use count : 2
    cout << "ptr_2 use count : " << ptr_2.use_count() << endl; // 输出:ptr_1 use count : 2

    // 编译错误
    // error C2440 : “初始化”: 无法从“std::weak_ptr<CA>”转换为“std::shared_ptr<CA>”
    // shared_ptr<CA> ptr_3 = wk_ptr;
}
  1. 测试weak_ptr常用函数的用法
    weak_ptr中只有函数lockexpired两个函数比较重要,因为它本身不会增加引用计数,所以它指向的对象可能在它用的时候已经被释放了,所以在用之前需要使用expired函数来检测是否过期,然后使用lock函数来获取其对应的shared_ptr对象,然后进行后续操作:
void test2()
{
    shared_ptr<CA> ptr_a(new CA());     // 输出:CA() called!
    shared_ptr<CB> ptr_b(new CB());     // 输出:CB() called!

    cout << "ptr_a use count : " << ptr_a.use_count() << endl; // 输出:ptr_a use count : 1
    cout << "ptr_b use count : " << ptr_b.use_count() << endl; // 输出:ptr_b use count : 1
    
    weak_ptr<CA> wk_ptr_a = ptr_a;
    weak_ptr<CB> wk_ptr_b = ptr_b;

    if (!wk_ptr_a.expired())
    {
        wk_ptr_a.lock()->show();        // 输出:this is class CA!
    }

    if (!wk_ptr_b.expired())
    {
        wk_ptr_b.lock()->show();        // 输出:this is class CB!
    }

    // 编译错误
    // 编译必须作用于相同的指针类型之间
    // wk_ptr_a.swap(wk_ptr_b);         // 调用交换函数

    wk_ptr_b.reset();                   // 将wk_ptr_b的指向清空
    if (wk_ptr_b.expired())
    {
        cout << "wk_ptr_b is invalid" << endl;  // 输出:wk_ptr_b is invalid 说明改指针已经无效
    }

    wk_ptr_b = ptr_b;
    if (!wk_ptr_b.expired())
    {
        wk_ptr_b.lock()->show();        // 输出:this is class CB! 调用赋值操作后,wk_ptr_b恢复有效
    }

    // 编译错误
    // 编译必须作用于相同的指针类型之间
    // wk_ptr_b = wk_ptr_a;


    // 最后输出的引用计数还是1,说明之前使用weak_ptr类型赋值,不会影响引用计数
    cout << "ptr_a use count : " << ptr_a.use_count() << endl; // 输出:ptr_a use count : 1
    cout << "ptr_b use count : " << ptr_b.use_count() << endl; // 输出:ptr_b use count : 1
}

现象分析

引用计数的出现,解决了对象独占的问题,但是也带来了循环引用的困扰,使用weak_ptr可以打破这种循环,当你理不清引用关系的时候,不妨采用文中画图的方式来理一理头绪,或许就会有眼前一亮的感觉。

总结

  1. weak_ptr虽然是一个模板类,但是不能用来直接定义指向原始指针的对象。
  2. weak_ptr接受shared_ptr类型的变量赋值,但是反过来是行不通的,需要使用lock函数。
  3. weak_ptr设计之初就是为了服务于shared_ptr的,所以不增加引用计数就是它的核心功能。
  4. 由于不知道什么之后weak_ptr所指向的对象就会被析构掉,所以使用之前请先使用expired函数检测一下。

测试源码

示例传送门:weak_ptr用法

<think>好的,我现在需要回答用户关于C++weak_ptr的计数器机制及其作用的问题。首先,我需要回顾一下自己关于weak_ptr的知识,并结合提供的引用资料来确保回答的准确性。 首先,weak_ptrC++中的一种智能指针,它通常与shared_ptr配合使用。根据引用资料,比如引用[1]提到,weak_ptr不会增加引用计数,只是监控shared_ptr管理的对象。这说明weak_ptr本身可能不直接管理对象的生命周期,而是依赖于shared_ptr的引用计数。 接下来,用户想知道weak_ptr内部的计数器机制。根据常见的C++知识,shared_ptr使用两个计数器:一个用于强引用(对象存在与否),另一个用于弱引用(比如weak_ptr的数量)。这可能就是weak_ptr的计数器机制。引用[4]提到weak_ptr是为了解决循环引用问题,这可能涉及到引用计数中的强引用和弱引用的区别。 需要确认weak_ptr内部是否有自己的计数器,或者它是否共享shared_ptr的控制块。根据引用[2],weak_ptr的构造函数可以接受shared_ptr,而它的expired()方法检查use_count()是否为0。这说明weak_ptr可能访问shared_ptr的控制块中的信息,如引用计数。而use_count()返回的是对应的shared_ptr的引用数,所以weak_ptr本身可能不维护自己的计数器,而是依赖shared_ptr的控制块中的弱引用计数。 引用[3]中的例子显示,当用shared_ptr初始化另一个shared_ptr时,引用计数会增加。而weak_ptr的初始化可能不会增加强引用计数,但可能会增加弱引用计数。例如,当有多个weak_ptr指向同一个对象时,控制块中的弱引用计数会增加,但这不影响对象的生命周期,只有当强引用计数为0时,对象才会被销毁,但控制块可能直到弱引用计数也为0时才会被释放。 所以总结,weak_ptr的计数器可能分为两部分:强引用计数(由shared_ptr管理)和弱引用计数(由weak_ptr管理)。当所有shared_ptr被销毁后,对象被释放,但控制块中的弱引用计数仍存在,直到最后一个weak_ptr被销毁,控制块才会被释放。这可能解释了为什么weak_ptr可以安全地判断对象是否存在,而不会导致悬空指针。 此外,weak_ptr的作用包括解决循环引用问题,如两个shared_ptr互相引用导致无法释放的情况。这时候将其中一个改为weak_ptr可以打破循环,因为weak_ptr不会增加强引用计数。引用[4]也提到这点,说明这是weak_ptr的主要用途之一。 现在需要将这些信息组织成用户需要的答案,包括计数器的工作原理和用途,同时引用提供的资料来支持观点。需要注意的是,用户可能对控制块的具体结构不太清楚,因此需要用比较易懂的语言解释强引用和弱引用计数的区别,以及它们如何协作管理对象的生命周期。</think>### C++ weak_ptr 的计数器机制及作用 #### 1. weak_ptr 的计数器机制 weak_ptr 的计数器机制与 shared_ptr 的控制块密切相关。shared_ptr 内部维护一个**控制块**,其中包含两个关键计数器: - **强引用计数器**:记录当前有多少个 shared_ptr 实例持有对象的所有权(直接影响对象生命周期)。 - **弱引用计数器**:记录当前有多少个 weak_ptr 或 shared_ptr 实例关联到该对象(不影响对象生命周期)。 当通过 shared_ptr 创建 weak_ptr 时: - **强引用计数器不变**:weak_ptr 不会增加强引用计数,因此不会阻止对象的销毁。 - **弱引用计数器递增**:weak_ptr 会通过控制块记录自身的存在,直到最后一个 weak_ptr 被销毁,弱引用计数器才会归零。 例如: ```cpp std::shared_ptr<int> sp = std::make_shared<int>(42); std::weak_ptr<int> wp(sp); // 强引用=1,弱引用=1 ``` 此时即使 `sp` 被销毁(强引用归零),只要 `wp` 存在(弱引用仍≥1),控制块会保留内存中的元数据,但对象本身会被释放[^1][^4]。 --- #### 2. weak_ptr 的用途 1. **解决循环引用问题** 当两个 shared_ptr 互相持有对方的引用时,会导致强引用计数无法归零,从而内存泄漏。将其中一个改为 weak_ptr 可打破循环: ```cpp struct Node { std::shared_ptr<Node> next; std::weak_ptr<Node> prev; // 使用 weak_ptr 避免循环引用 }; ``` 此时 `prev` 的弱引用不会影响对象生命周期。 2. **安全访问临时对象** 通过 `weak_ptr::lock()` 方法可以临时获取 shared_ptr,若对象未销毁则安全使用: ```cpp auto sp = wp.lock(); // 返回 shared_ptr 或 nullptr if (sp) { // 安全操作 } ``` 这种方法常用于多线程环境中监控对象的有效性[^2]。 3. **降低资源占用** weak_ptr 不持有对象所有权,适合用于缓存或观察者模式,避免不必要的资源持有。 --- #### 3. 计数器生命周期示例 - **对象销毁条件**:强引用计数归零时,对象立即销毁。 - **控制块销毁条件**:强引用和弱引用计数均归零时,控制块内存才会释放。 例如: ```cpp { std::shared_ptr<int> sp(new int(42)); // 强=1,弱=0 std::weak_ptr<int> wp(sp); // 强=1,弱=1 } // sp 销毁后,强=0 → 对象销毁,但控制块保留(弱=1) // wp 销毁后,弱=0 → 控制块释放 ``` ---
评论 63
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AlbertS

常来“玩”啊~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值