【C++入门到精通】智能指针 shared_ptr循环引用 | weak_ptr 简介及C++模拟实现 [ C++入门 ]

在这里插入图片描述

阅读导航

  • 引言
  • 一、std::shared_ptr的循环引用
    • 1. 概念
    • 2. 示例分析
  • 二、std::weak_ptr
    • 1. 简介
    • 2. weak_ptr模板类提供的成员方法
    • 3. 使用示例
      • (1)weak_ptr指针的创建
      • (2)完整示例(解决上面循环引用问题)
    • 4. C++模拟实现
  • 温馨提示

引言

欢迎阅读本系列文章的第二篇,我们将继续探讨与 shared_ptr 相关的主题。上一篇文章我们介绍了 shared_ptr 的强大功能,但也提到了它可能面临的一个问题 —— 循环引用。当两个或多个对象之间相互持有 shared_ptr 的引用时,就会形成循环引用,导致这些对象无法被正确释放,从而引发内存泄漏。

在本文中,我们将深入讨论循环引用问题,并引入另一个智能指针类——weak_ptrweak_ptrshared_ptr 的伙伴,它可以帮助我们解决循环引用问题,并且不会增加引用计数,以避免对象无法释放的情况

通过学习 shared_ptrweak_ptr 的组合使用,我们将能够更好地管理动态分配的对象,避免内存泄漏,并提高代码的健壮性和可维护性。敬请期待本文的剖析和示例,希望能给您带来更深入的了解和实践经验。

一、std::shared_ptr的循环引用

1. 概念

当使用 std::shared_ptr 时,循环引用是一种常见的问题。循环引用指的是两个或多个对象彼此持有 shared_ptr 的引用,形成一个环状依赖关系。这种情况下,即使没有外部引用指向这些对象,它们的引用计数也无法降为零,从而导致内存泄漏

循环引用可能会导致内存泄漏的发生,因为每个对象都会持有对其他对象的引用,导致它们的引用计数无法归零。当没有外部引用指向这些对象时,它们的析构函数不会被调用,从而导致资源无法正确释放。

2. 示例分析

首先我们来看一段代码,这段代码就明显存在着循环引用。

struct ListNode
{int _data;shared_ptr<ListNode> _prev;shared_ptr<ListNode> _next;~ListNode(){ cout << "~ListNode()" << endl; }
};
int main()
{shared_ptr<ListNode> node1(new ListNode);shared_ptr<ListNode> node2(new ListNode);cout << node1.use_count() << endl;cout << node2.use_count() << endl;node1->_next = node2;node2->_prev = node1;cout << node1.use_count() << endl;cout << node2.use_count() << endl;return 0;
}

循环引用分析

  1. node1node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动delete
  2. node1_next指向node2node2_prev指向node1,引用计数变成2。
  3. node1node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点。
  4. 也就是说_next析构了,node2就释放了
  5. 也就是说_prev析构了,node1就释放了
  6. 但是_next属于node的成员,node1释放了,_next才会析构,而node1_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放

⭕让我们通过下面这个图片来说上面这个问题:
在这里插入图片描述

为了解决循环引用问题,可以使用 std::weak_ptrstd::weak_ptr 是一种弱引用,它可以指向 std::shared_ptr 持有的对象,但不会增加对象的引用计数。这样,即使存在循环引用,通过使用 std::weak_ptr 可以打破循环引用,使对象的引用计数能够正确降为零,从而触发析构函数的调用。

二、std::weak_ptr

1. 简介

std::weak_ptr 是 C++11 标准库中提供的一种弱引用智能指针,它可以指向 std::shared_ptr 所管理的对象,但不会增加对象的引用计数。因此,当使用 std::weak_ptr 时,如果 std::shared_ptr 对象被释放或者过期,std::weak_ptr 将自动失效,避免了循环引用导致的内存泄漏问题

🔴std::weak_ptr官方文档

在这里插入图片描述

2. weak_ptr模板类提供的成员方法

shared_ptr<T>unique_ptr<T> 相比,weak_ptr<T> 模板类提供的成员方法不多,下表罗列了常用的成员方法及各自的功能。

成员方法功能
operator=重载 = 赋值运算符,使得 std::weak_ptr 指针可以直接被 std::weak_ptr 或者 std::shared_ptr 类型指针赋值。
swap(x)其中 x 表示一个同类型的 std::weak_ptr 类型指针,该函数可以互换两个同类型 std::weak_ptr 指针的内容。
reset()将当前 std::weak_ptr 指针置为空指针。
use_count()查看指向和当前 std::weak_ptr 指针相同的 std::shared_ptr 指针的数量。
expired()判断当前 std::weak_ptr 指针是否过期(指针为空,或者指向的堆内存已经被释放)。
lock()如果当前 std::weak_ptr 已经过期,则该函数会返回一个空的 std::shared_ptr 指针;反之,该函数返回一个和当前 std::weak_ptr 指向相同的 std::shared_ptr 指针。

🚨🚨注意:weak_ptr<T> 模板类没有重载 *-> 运算符,因此 weak_ptr 类型指针只能访问某一 shared_ptr 指针指向的堆内存空间,无法对其进行修改

3. 使用示例

(1)weak_ptr指针的创建

创建 std::weak_ptr 指针的方式和创建 std::shared_ptr 的方式类似。下面列举了三种常见的创建 std::weak_ptr 的方式:

  1. std::shared_ptr 创建

可以通过将 std::shared_ptr 赋值给 std::weak_ptr 来创建一个弱引用指针,例如:

std::shared_ptr<int> sptr = std::make_shared<int>(42);
std::weak_ptr<int> wptr(sptr);

上述代码中,我们首先创建了一个 std::shared_ptr 对象 sptr,它指向一个动态分配的 int 类型对象。然后,我们将 sptr 赋值给 std::weak_ptr 对象 wptr,创建了一个弱引用指针。

  1. std::shared_ptr 转换

可以通过 std::shared_ptrweak_ptr 成员函数,将 std::shared_ptr 转换为 std::weak_ptr,例如:

std::shared_ptr<int> sptr = std::make_shared<int>(42);
std::weak_ptr<int> wptr = sptr->weak_ptr();

上述代码中,我们首先创建了一个 std::shared_ptr 对象 sptr,它指向一个动态分配的 int 类型对象。然后,我们调用 sptrweak_ptr() 成员函数,将 sptr 转换为 std::weak_ptr 对象 wptr,创建了一个弱引用指针。

  1. 使用 std::weak_ptr 的构造函数

可以直接使用 std::weak_ptr 的构造函数,创建一个空的弱引用指针,例如:

std::weak_ptr<int> wptr;

上述代码中,我们直接创建了一个空的 std::weak_ptr 对象 wptr,它不持有任何对象的引用。

(2)完整示例(解决上面循环引用问题)

使用 std::weak_ptr 修改上面的代码,可以将 _prev_next 成员变量改为 std::weak_ptr<ListNode> 类型。这样可以避免循环引用,同时仍然可以访问链表中的前一个节点和后一个节点

struct ListNode
{int _data;weak_ptr<ListNode> _prev;weak_ptr<ListNode> _next;~ListNode() { cout << "~ListNode()" << endl; }
};int main()
{shared_ptr<ListNode> node1(new ListNode);shared_ptr<ListNode> node2(new ListNode);cout << node1.use_count() << endl;cout << node2.use_count() << endl;node1->_next = node2;node2->_prev = node1;cout << node1.use_count() << endl;cout << node2.use_count() << endl;// 使用 weak_ptr.lock() 获取 shared_ptr 对象shared_ptr<ListNode> node1Next = node1->_next.lock();shared_ptr<ListNode> node2Prev = node2->_prev.lock();if (node1Next)cout << "node1 next data: " << node1Next->_data << endl;elsecout << "node1 next is nullptr" << endl;if (node2Prev)cout << "node2 prev data: " << node2Prev->_data << endl;elsecout << "node2 prev is nullptr" << endl;return 0;
}

运行上述代码,可以得到如下输出:

1
1
2
2
node1 next data: 0
node2 prev data: 0

在上面的代码示例中,我们创建了两个节点 node1node2,并通过 std::weak_ptr 进行相互引用。

shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);

接下来,我们设置节点之间的关系。修改前的代码如下:

node1->_next = node2;
node2->_prev = node1;

我们将 _next_prev 成员变量的类型从 shared_ptr<ListNode> 改为 weak_ptr<ListNode>

weak_ptr<ListNode> _prev;
weak_ptr<ListNode> _next;

这样就建立了节点之间的弱引用关系,避免了循环引用。

接下来,我们可以通过 lock() 方法将 std::weak_ptr 转换为 std::shared_ptr,以访问所管理的对象。

shared_ptr<ListNode> node1Next = node1->_next.lock();
shared_ptr<ListNode> node2Prev = node2->_prev.lock();

如果 std::weak_ptr 不过期(即所管理的对象还存在),lock() 方法会返回一个有效的 std::shared_ptr 对象,否则返回空指针。

最后,我们可以使用这些 std::shared_ptr 对象来访问链表中的前驱和后继节点的数据。

if (node1Next)cout << "node1 next data: " << node1Next->_data << endl;
elsecout << "node1 next is nullptr" << endl;if (node2Prev)cout << "node2 prev data: " << node2Prev->_data << endl;
elsecout << "node2 prev is nullptr" << endl;

通过这种方式,我们可以安全地访问链表中的前驱和后继节点,而不会导致循环引用和内存泄漏。

4. C++模拟实现

template<class T>
class weak_ptr
{
public:// 默认构造函数,将_ptr成员指针初始化为nullptrweak_ptr():_ptr(nullptr){}// 接受shared_ptr参数的构造函数,将_ptr成员指针初始化为shared_ptr所管理对象的指针weak_ptr(const shared_ptr<T>& sp):_ptr(sp.get()){}// 重载*运算符,返回所管理对象的引用T& operator*(){return *_ptr;}// 重载->运算符,返回所管理对象的指针T* operator->(){return _ptr;}// 返回所管理对象的指针T* get(){return _ptr;}private:T* _ptr; // 所管理对象的指针
};

这段代码是一个简化版的 weak_ptr 类的实现,提供了一些基本的功能。

首先,我们可以看到 weak_ptr 类有一个默认构造函数和一个接受 shared_ptr 参数的构造函数。默认构造函数将 _ptr 成员指针初始化为 nullptr,而接受 shared_ptr 参数的构造函数将 _ptr 成员指针初始化为 shared_ptr 所管理对象的指针。

接下来,我们可以看到 weak_ptr 重载了 *-> 运算符,使得可以像使用指针一样,通过 weak_ptr 对象访问所管理的对象。operator*() 返回所管理对象的引用,operator->() 返回所管理对象的指针。

同时,weak_ptr 还提供了一个 get() 方法,返回 _ptr 指针,即所管理对象的指针。这允许用户直接访问 _ptr 指针,但需要注意,这种直接访问可能会导致悬空指针问题,因为 _ptr 指针可能已经无效(所管理对象已被释放)。

🚨🚨注意:这个简化版的 weak_ptr 实现没有考虑线程安全性。在实际应用中,weak_ptr 需要与其他智能指针共同使用,比如 shared_ptrunique_ptr,并且需要考虑线程安全性和异常安全性

温馨提示

感谢您对博主文章的关注与支持!另外,我计划在未来的更新中持续探讨与本文相关的内容,会为您带来更多关于C++以及编程技术问题的深入解析、应用案例和趣味玩法等。请继续关注博主的更新,不要错过任何精彩内容!

再次感谢您的支持和关注。期待与您建立更紧密的互动,共同探索C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/245117.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微信小程序(九)轮播图

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.轮播容器的基本属性 2.轮播图片的尺寸处理 index.wxml <view class"navs"><text class"active">精选</text><text>手机</text><text>食品</text…

应用案例:Ruff工业设备数据采集,为生产制造企业数字化转型赋能

导读&#xff1a;某金属材料生产制造企业&#xff0c;引进了整套Ruff数据采集方案&#xff0c;将Ruff网关采集到的PLC数据接入到Ruff IoT管理云平台&#xff0c;帮助客户实现覆盖全厂区、车间所有设备的数字化、可视化管理&#xff0c;避免了意外停机风险&#xff0c;IT运维工作…

C# 实现 Word 加盖骑缝章效果

目录 实现效果 范例运行环境 Office DCOM 配置 设计实现 创建stamp图章类 电子章图片的计算与定位 旋转图片方法 总结 实现效果 在OA的自动化处理系统中&#xff0c;通过审批的最终节点&#xff0c;可能会对WORD文件加盖电子章&#xff0c;比如定位带有指定文字的Ra…

洛谷刷题-【入门2】分支结构

目录 1.苹果和虫子 题目描述 输入格式 输出格式 输入输出样例 2.数的性质 题目描述 输入格式 输出格式 输入输出样例 3.闰年判断 题目描述 输入格式 输出格式 输入输出样例 4.apples 题目描述 输入格式 输出格式 输入输出样例 5.洛谷团队系统 题目描述 …

MySQL(基础篇)——SQL

一.SQL分类 二.DDL(数据定义语言) 1.DDL——数据库操作 ① 查询 查询所有数据库 SHOW DATABASES 查询当前所处数据库 SELECT DATABASE() ② 创建 CREATE DATABASE [IF NOT EXISTS] 数据库名(通常以db结尾) [DEFAULT CHARSET 字符集] [COLLATE 排序规则] ③ …

【网络安全 -> 防御与保护】专栏文章索引

为了方便 快速定位 和 便于文章间的相互引用等 作为一个快速准确的导航工具 网络安全——防御与保护 &#xff08;一&#xff09;.信息安全概述 &#xff08;二&#xff09;.防火墙组网

第04章_IDEA的安装与使用(上)(认识,卸载与安装,JDK相关设置,详细设置,工程与模块管理,代码模板的使用)

文章目录 第04章_IDEA的安装与使用&#xff08;上&#xff09;本章专题与脉络1. 认识IntelliJ IDEA1.1 JetBrains 公司介绍1.2 IntelliJ IDEA 介绍1.3 IDEA的主要优势&#xff1a;(vs Eclipse)1.4 IDEA 的下载 2. 卸载与安装2.1 卸载过程2.2 安装前的准备2.3 安装过程2.4 注册2…

逻辑回归中的损失函数梯度下降

一、引言 逻辑回归中的损失函数通常采用的是交叉熵损失函数&#xff08;cross-entropy loss function&#xff09;。在逻辑回归中&#xff0c;我们通常使用sigmoid函数将线性模型的输出转换为概率值&#xff0c;然后将这些概率值与实际标签进行比较&#xff0c;从而计算损失。 …

《统计学习方法:李航》笔记 从原理到实现(基于python)-- 第 2章感知机

文章目录 第 2章感知机2.1 感知机模型2.2 感知机学习策略2.2.1 数据集的线性可分性2.2.2 感知机学习策略 2.3 感知机学习算法2.3.1 感知机学习算法的原始形式2.3.2 算法的收敛性2.3.3 感知机学习算法的对偶形式 实践&#xff1a;二分类模型&#xff08;iris数据集&#xff09;数…

web漏洞总结大全(基础)

前言 本文章是和cike_y师傅一起写的&#xff0c;cike_y博客&#xff1a;https://blog.csdn.net/weixin_53912233?typeblog 也欢迎大家对本文章进行补充和指正&#xff0c;共同维护这个项目&#xff0c;本文的github项目地址&#xff1a; https://github.com/baimao-box/Sum…

Linux系统Shell脚本编程之条件语句

一、条件测试 Shell 环境根据命令执行后的返回状态值 " $? " 来判断是否执行成功&#xff0c;当返回值为0时表示成功&#xff0c;否则表示失败或异常&#xff08;非0值&#xff09;。使用专门的测试工具 test 命令&#xff0c;可以对特定条件进行测试&#xff0c;并…

VI / VIM的使用

vi/vim 的区别简单点来说&#xff0c;它们都是多模式编辑器&#xff0c;不同的是 vim 是 vi 的升级版本&#xff0c;它不仅兼容 vi 的所有指令&#xff0c;而且 还有一些新的特性在里面。例如语法加亮&#xff0c;可视化操作不仅可以在终端运行&#xff0c;也可以运行于 x win…

【Linux】糟糕,是心动的感觉——与Linux的初次相遇

初识Linux 导言一、计算机的发展1.1 历史背景1.2 计算机的发明 二、操作系统2.1 什么是操作系统&#xff1f;2.2 操作系统的诞生2.3 操作系统的发展2.3.1 批处理系统的发展2.3.2 分时系统2.3.3 实时系统2.3.4 通用操作系统 2.4 UNIX操作系统2.4.1 UNIX的诞生2.4.2 UNIX的发展 2…

Git学习 -- 分支合并、版本修改相关

目录 learn GIT Learn Git Branching merge和rebase的使用 基础命令 版本回退 工作区和暂存区 管理修改 撤销修改 删除修改 learn GIT Learn Git Branching 这是Gitee上的Git学习教程 Learn Git Branching Git Rebase Learn Git Branching 最终的实操 merge和rebase的…

爬虫正则+bs4+xpath+综合实战详解

Day3 - 1.数据解析概述_哔哩哔哩_bilibili 聚焦爬虫&#xff1a;爬取页面中指定的页面内容 编码流程&#xff1a;指定url -> 发起请求 -> 获取响应数据 -> 数据解析 -> 持久化存储 数据解析分类&#xff1a;正则、bs4、xpath(本教程的重点) 数据解析原理概述&am…

2024年 全新 HTTP 客户端 你用了?

我们平时开发项目的时候&#xff0c;经常会需要远程调用下其他服务提供的接口&#xff0c;于是我们会使用一些 HTTP 工具类比如 Hutool 提供的 HttpUtil。SpringBoot 3.0 出了一个Http Interface的新特性&#xff0c;它允许我们使用声明式服务调用的方式来调用远程接口&#xf…

重磅!Salesforce推出UE+无限版餐套,企业如何选择?

孤立的应用程序和脱节的技术堆栈是所有企业的噩梦。目前&#xff0c;市场上产品和服务种类繁多&#xff0c;无缝集成和清晰、直接的购买流程可以决定整体体验的成败。 Salesforce云以及其他应用程序在过去几年中经历了巨大增长。随着越来越多的功能被捆绑在一起&#xff0c;Un…

威联通QNAP NAS结合cpolar内网穿透实现公网远程访问NAS中存储的文件

文章目录 推荐 前言1. 威联通安装cpolar内网穿透2. 内网穿透2.1 创建隧道2.2 测试公网远程访问 3. 配置固定二级子域名3.1 保留二级子域名3.2 配置二级子域名 4. 使用固定二级子域名远程访问 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣…

Tarjan 算法(超详细!!)

推荐在 cnblogs 上阅读 Tarjan 算法 前言 说来惭愧&#xff0c;这个模板仅是绿的算法至今我才学会。 我还记得去年 CSP2023 坐大巴路上拿着书背 Tarjan 的模板。虽然那年没有考连通分量类似的题目。 现在做题遇到了 Tarjan&#xff0c;那么&#xff0c;重学&#xff0c;开…

“趣味夕阳,乐享生活”小组活动(第二节)

立冬以来&#xff0c;天气日渐寒冷&#xff0c;气温变化较大&#xff0c;各种传染病多发&#xff0c;为进一步增强老年人冬季预防传染病保健意识及科学合理健康的生活方式。近日&#xff0c;1月22日&#xff0c;南阳市人人社工灌涨站开展了“趣味夕阳&#xff0c;乐享生活”小组…