C++内存管理基础中shared_ptr的循环引用问题解决

shared_ptr循环引用导致内存泄漏,通过weak_ptr打破循环。示例中A强引用B,B弱引用A,避免了析构时引用计数无法归零的问题,确保对象正确销毁。

c++内存管理基础中shared_ptr的循环引用问题解决

shared_ptr

循环引用是C++内存管理中一个常见的陷阱,它会导致对象无法被正确销毁,进而引发内存泄漏。解决这个问题的核心思路是打破对象间强引用的循环,通常通过引入

weak_ptr

来将循环中的一个强引用替换为弱引用。

weak_ptr

不增加对象的引用计数,允许对象在没有其他强引用时被正常销毁,从而避免泄漏。

#include #include #include // 前向声明,因为A和B互相引用class B;class A {public:    std::string name;    std::shared_ptr b_ptr; // A强引用B    A(std::string n) : name(n) {        std::cout << "A " << name << " constructor\n";    }    ~A() {        std::cout << "A " << name << " destructor\n";    }    void set_b(std::shared_ptr b) {        b_ptr = b;    }};class B {public:    std::string name;    std::weak_ptr a_ptr; // B弱引用A,这是打破循环的关键    B(std::string n) : name(n) {        std::cout << "B " << name << " constructor\n";    }    ~B() {        std::cout << "B " << name << " destructor\n";    }    void set_a(std::shared_ptr a) {        a_ptr = a;    }    void check_a_status() {        if (auto shared_a = a_ptr.lock()) { // 尝试提升为shared_ptr            std::cout << "B " << name << " observes A " <name << " is still alive.\n";        } else {            std::cout << "B " << name << " observes A is gone.\n";        }    }};int main() {    std::cout << "--- 示例开始 ---\n";    {        std::shared_ptr pa = std::make_shared("Object_A");        std::shared_ptr pb = std::make_shared("Object_B");        std::cout << "初始引用计数:pa=" << pa.use_count() << ", pb=" << pb.use_count() <set_b(pb); // A强引用B,B的引用计数增加        pb->set_a(pa); // B弱引用A,A的引用计数不变        std::cout << "建立引用后:pa=" << pa.use_count() << ", pb=" << pb.use_count() <check_a_status(); // B尝试访问A,此时A应该还活着    } // pa和pb超出作用域,智能指针自动析构    std::cout << "--- 示例结束 ---\n";    // 如果没有循环引用,A和B的析构函数都会被调用    return 0;}

为什么我的

shared_ptr

对象迟迟不销毁?深入理解循环引用是如何形成的

我们经常会遇到这样的情况:你用

shared_ptr

管理资源,觉得万无一失了,结果程序跑着跑着,内存占用就上去了,那些本该被释放的对象却还在内存里躺着。这多半就是循环引用在作祟。

它的形成其实很简单,就是两个或多个对象,它们之间相互持有对方的

shared_ptr

。比如,A对象有一个

shared_ptr

成员,同时B对象也有一个

成员。当它们各自初始化并相互引用后,A的引用计数会因为B持有它而增加,B的引用计数也会因为A持有它而增加。

想象一下这个过程:你创建了一个

,此时A的引用计数是1。接着你创建了一个

shared_ptr pb

,B的引用计数也是1。然后,你让

pa

内部的成员指向

pb

。这时,B的引用计数从1变成了2(

pb

持有一次,

pa

内部持有一次)。再接着,你让

pb

内部的成员指向

pa

。这时,A的引用计数从1变成了2(

pa

持有一次,

pb

内部持有一次)。

现在问题来了,当

pa

pb

这两个局部变量超出作用域时,它们会尝试销毁自己持有的

shared_ptr

pa

销毁,A的引用计数从2变成1。

pb

销毁,B的引用计数从2变成1。但注意,A的内部仍然持有一个

shared_ptr

指向B,B的内部也仍然持有一个

shared_ptr

指向A。这意味着A和B的引用计数永远不会降到0。它们会一直“互相指着对方”,谁也无法先走一步,最终导致内存泄漏。

立即学习“C++免费学习笔记(深入)”;

这就像两个人手拉着手站在悬崖边,谁也松不开,因为一旦松开,对方就可能掉下去。但实际上,只要有一个人愿意放手,或者至少不是用“死死抓住”的方式握着,这个僵局就能打破。这种“死死抓住”就是

shared_ptr

的强引用特性,它确保只要有一个

shared_ptr

存在,对象就不会被销毁。

深入浅出

weak_ptr

:它是如何巧妙地打破循环引用的?

weak_ptr

,正如其名,是一个“弱”指针。它不拥有对象的所有权,也不会增加对象的引用计数。这正是它能打破循环引用的关键所在。你可以把它理解成一个“观察者”或者“旁观者”,它只是知道某个对象可能存在,但它不参与对象的生命周期管理。

当我们用

weak_ptr

替换循环引用中的一个

shared_ptr

时,比如在上面的A和B的例子中,让B持有

而不是

。那么,整个引用链条就会变成这样:

pa

持有

,A的引用计数为1。

pb

持有

shared_ptr

,B的引用计数为1。

pa

内部持有

shared_ptr

,B的引用计数变为2。

pb

内部持有

。注意,

weak_ptr

不会增加A的引用计数,所以A的引用计数仍然是1。

现在,当

pa

pb

超出作用域时:

pa

销毁,A的引用计数从1降到0。此时,A对象被销毁。

pb

销毁,B的引用计数从2降到1(只剩下

pa

内部持有的那个)。

等A被销毁后,

pb

内部的那个

就会自动失效(

expired()

方法会返回true)。然后,当

pa

内部的

shared_ptr

也超出作用域(或者说A对象被销毁时,其成员

b_ptr

也会被销毁),B的引用计数从1降到0。B对象也被销毁。

看,这样一来,A和B都能被正常销毁了。

weak_ptr

就像是循环中的一个“软连接”,它允许你访问对象,但不会阻碍对象的销毁。当你需要使用

weak_ptr

指向的对象时,你必须先调用它的

lock()

方法,尝试将其提升为一个

shared_ptr

。如果对象仍然存在,

lock()

会返回一个有效的

shared_ptr

;如果对象已经被销毁了,它会返回一个空的

shared_ptr

。这提供了一种安全地访问可能已被销毁对象的方式,避免了悬空指针的问题。

这种机制在父子关系、观察者模式或者缓存管理中非常有用。比如一个父节点拥有子节点,子节点需要知道它的父节点是谁,但子节点不应该拥有父节点,否则就会形成循环。这时,子节点持有父节点的

weak_ptr

就是非常自然且正确的选择。

除了打破循环引用,

weak_ptr

还有哪些实用的应用场景?

weak_ptr

的作用远不止打破循环引用这么简单,它在很多场景下都能发挥独特的优势,主要体现在需要“观察”但不“拥有”对象所有权的需求上。

一个很典型的场景就是观察者模式(Observer Pattern)。在观察者模式中,一个主题(Subject)对象会维护一个观察者(Observer)列表,并在状态改变时通知所有观察者。如果主题持有观察者的

shared_ptr

,而观察者又反过来持有主题的

shared_ptr

(比如为了获取主题的信息),那么就会形成循环引用。正确的做法是,主题持有观察者的

weak_ptr

。这样,当一个观察者不再被其他地方强引用时,它就可以被销毁,而主题并不会阻止它的销毁。主题在通知观察者时,只需要尝试

lock()

这个

weak_ptr

,如果成功,就说明观察者还活着,可以通知;如果失败,说明观察者已经“去世”了,可以将其从列表中移除。

另一个常见应用是缓存(Cache)机制。假设你有一个对象缓存,里面存放着一些昂贵的对象。你希望这些对象在没有其他地方使用它们时能够被自动清理,以节省内存。如果缓存直接持有这些对象的

shared_ptr

,那么只要对象在缓存里,它的引用计数就不会降到0,永远不会被清理。这时,缓存就应该持有这些对象的

weak_ptr

。当外部代码需要某个对象时,它可以通过缓存获取一个

shared_ptr

。当所有外部

shared_ptr

都失效后,对象就会被销毁,缓存中的

weak_ptr

也会随之失效。下次请求时,缓存发现

weak_ptr

失效了,就知道这个对象已经被清理,可以重新创建或

以上就是C++内存管理基础中shared_ptr的循环引用问题解决的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1475145.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
C++结构体与指针结合使用技巧
上一篇 2025年12月18日 23:08:17
C++对象构造与析构性能优化方法
下一篇 2025年12月18日 23:08:36

相关推荐

  • VSCode如何实现代码热重载 VSCode实时预览开发的高效配置方案

    使用live server扩展实现静态文件的实时预览,保存后浏览器自动刷新;2. 利用现代前端框架(如react、vue)内置的开发服务器(如vite、webpack dev server)实现hmr热模块替换,修改代码后仅更新变动模块而不刷新页面;3. 结合browsersync等工具实现多设备同…

    2026年9月24日
    000
  • T1与IG宿命交锋:电子竞技史上新篇章,10月14日上演巅峰对决

    每年秋天,《英雄联盟》全球总决赛总能掀起电子竞技的风暴,而今年的战场,北京,无疑是这场战役的焦点之一。四号种子对决,t1和ig即将上演的这场殊死搏斗,让人血脉偾张。这不仅仅是战队之间的较量,更像是电子竞技的”逆袭神话”能否继续书写的关键。然而站在t1的角度,今年的征途并不轻松…

    2026年9月24日
    200
  • 问界M5现身美国加州公路 测试车还是平行进口车?

    近日,小编注意到,有网友在社交平台发布照片称,自己于美国加州i-880公路拍摄到悬挂当地车牌的问界m5车型,引发关于该车身份的猜测:是华为官方测试车辆,还是私人渠道引入的平行进口车? ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜ 问界M5 …

    2026年9月24日
    000
  • 外媒测试《消逝的光芒:困兽》PC性能:运行表现相当优秀

    外媒测试《消逝的光芒:困兽》PC性能:运行表现相当优秀外媒测试《消逝的光芒:困兽》PC性能:运行表现相当优秀外媒测试《消逝的光芒:困兽》PC性能:运行表现相当优秀外媒测试《消逝的光芒:困兽》PC性能:运行表现相当优秀

    来入手《消逝的光芒:困兽》吧!现享金币优惠叠加专属优惠券折上折,标准版仅需200.9元(共节省47.1元);豪华版233.2元(总计立减54.8元)。 由Techland打造的《消逝的光芒》系列新作《消逝的光芒:困兽》已正式上线。本作背景设定在曾经风景如画、如今却尸横遍野的河狸谷。玩家将在此组建临时…

    2026年9月24日 用户投稿
    000
  • mysql临时表如何使用_PHP中操作mysql临时表的具体步骤

    MySQL临时表仅在当前会话可见,连接关闭后自动删除,适合中间数据处理。使用PHP操作时,先通过mysqli或PDO建立数据库连接,再执行CREATE TEMPORARY TABLE语句创建临时表,随后可像普通表一样进行INSERT、SELECT及JOIN等操作。临时表可与永久表同名且优先被使用,支…

    2026年9月24日
    000
  • UC浏览器怎么查看和清除LocalStorage数据 UC浏览器LocalStorage数据管理方法

    可通过隐私设置清除或开发者工具查看LocalStorage。①在UC浏览器设置中选择“隐私与安全”→“清除浏览数据”,勾选“Cookie及其他网站数据”即可批量删除LocalStorage;②打开uc://inspect启用开发者工具,通过电脑Chrome远程调试查看具体键值对;③root设备后使用…

    2026年9月24日
    200
  • Java语法基础中static关键字可以修饰哪些内容

    static关键字用于定义类成员,包括静态变量(如计数器)、静态方法(如工具方法)、静态代码块(类加载时执行)和静态内部类(不依赖外部类实例),均属于类而非对象,通过类名访问,提升成员至类级别实现共享与提前使用。 static 关键字在 Java 中主要用于定义与类相关而非与对象实例相关的成员。它不…

    2026年9月24日
    100
  • 强制联网! 《战地6》单人战役模式无法离线游玩

    强制联网! 《战地6》单人战役模式无法离线游玩强制联网! 《战地6》单人战役模式无法离线游玩强制联网! 《战地6》单人战役模式无法离线游玩强制联网! 《战地6》单人战役模式无法离线游玩

    《战地6》本有机会打破近年来游戏行业普遍采用的“光盘仅附激活码”的做法——这一次,所有游戏内容都被完整地刻录在了实体光盘中。然而令人失望的是,即便如此,玩家若想体验单人战役,依然必须保持网络连接。 这一设定带来了诸多问题。对于只想沉浸于单人剧情、无意参与多人对战的玩家来说,他们仍需和数百万在线用户一…

    2026年9月24日 用户投稿
    000
  • 全模组电源比非模组电源好在哪里?

    全模组电源优势在于线材管理、升级便利和系统整洁。其线材可自由拆装,减少冗余,提升散热与走线美观;支持多平台共用和单独更换线材,便于升级与故障处理;适合追求整洁与DIY的用户,虽价格较高但灵活性更强。 全模组电源相比非模组电源,主要优势体现在线材管理、升级便利性和整体系统整洁度上。它并不是在性能或效率…

    2026年9月24日
    100
  • 抖音怎么看注册时间?怎么看百度网盘注册时间

    抖音已然成为国内炙手可热的短视频平台之一。凭借其独特的智能推荐系统,用户能够在短时间内找到自己喜爱的内容。你是否知道,你的抖音注册时间实际上隐含了许多关于你的社交轨迹的信息呢?本文将带领大家一同揭秘抖音注册时间背后的故事。 一、抖音注册时间的意义 1. 用户活跃程度的体现 抖音注册时间能够帮助我们判…

    2026年9月24日
    100
  • mysql中*是什么意思 mysql星号通配符解析

    在 mysql 中,星号()最常用于 select 语句中代表所有列,但应谨慎使用。1)它方便查看所有数据,但可能返回不必要的数据,影响性能。2)使用可能降低代码可维护性,建议明确列出所需列。3)在like操作符中,不是通配符,需用regexp。4)在视图中使用可能导致定义失效。5)可结合limit…

    2026年9月24日
    000
  • 为什么要4k对齐

    早期硬盘的每个扇区以512字节为标准,而新一代硬盘的扇区容量则为4096个字节,即所谓的4k扇区。虽然硬盘标准已经更新,但操作系统仍然使用512字节扇区的标准。为了确保兼容性,硬盘制造商将4k扇区模拟成了512字节扇区。文件系统的块(簇)通常是512字节的倍数,而新系统大多设定为4k的倍数,例如li…

    2026年9月24日
    000
  • Yii框架的验证器:如何确保输入数据的正确性

    yii框架是一款高效、快速的php框架,提供了丰富的功能和组件,使开发者能够快速构建web应用程序。其中,验证器是yii框架中一个非常重要的组件,可以确保输入数据的正确性。本文将介绍yii框架的验证器是如何工作的,以及如何使用验证器来确保输入数据的正确性。 Yii框架验证器的工作原理 在Yii框架中…

    用户投稿 2026年9月24日
    100
  • 罗技gpw鼠标驱动如何调节DPI?罗技gpw鼠标驱动调节DPI的方法

    罗技gpw鼠标驱动如何调节DPI?罗技gpw鼠标驱动调节DPI的方法罗技gpw鼠标驱动如何调节DPI?罗技gpw鼠标驱动调节DPI的方法罗技gpw鼠标驱动如何调节DPI?罗技gpw鼠标驱动调节DPI的方法罗技gpw鼠标驱动如何调节DPI?罗技gpw鼠标驱动调节DPI的方法

    罗技gpw鼠标驱动专为gpw打造,提供毫秒级响应速度,支持一键关闭板载模式后自由调节dpi。在灵敏度设置界面中,只需拖动滑块即可精准调整当前dpi值,并可最多预设4个档位,通过鼠标上的dpi按键实现快速切换。 罗技gpw鼠标驱动如何调节DPI?详细操作步骤如下: 首先使用数据线将鼠标连接至电脑,并在…

    2026年9月24日 用户投稿
    200
  • 1-6月乘用车HUD标配装配量TOP20车型:问界M9第四

      近日,有机构公布了2025年1至6月国内乘用车hud功能标配装配量top20车型。其中,理想l6、l7、l8、l9和问界m9、m8等车型装配率(装配率=装配量/零售量)均为100%。此外,6月国内hud标配装配率接近20%。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 …

    2026年9月24日
    800
  • mac怎么合并访达窗口_mac Finder窗口合并技巧

    可通过“窗口”菜单中的“合并所有窗口”命令将多个访达窗口整合为标签页;2. 也可手动拖动窗口标题栏至另一窗口顶部标签栏以创建标签页;3. 结合Control+上箭头进入台前调度,可辅助分组管理并优化多窗口操作体验。 如果您在使用Mac时打开了多个“访达”窗口,导致桌面显得杂乱,可以通过合并窗口的方式…

    2026年9月24日
    000
  • realmeQ系列应用删不了怎么办?教你解决系统应用的卸载难题

    无法卸载realme Q系列预装应用时,首先尝试通过应用信息页面卸载;若失败,可前往软件商店修复或更新;仍无效则进入安全模式卸载;最后检查设备管理权限并清除数据缓存以解除限制。 如果您尝试从realme Q系列手机中删除某个应用,但发现无法完成卸载操作,这通常是因为该应用属于系统预装程序或具有特殊权…

    2026年9月24日
    000
  • Yii框架中的认证授权:保护应用程序安全

    随着互联网的快速发展,安全问题成为越来越多公司和组织关注的话题。在这个时代中,任何一个应用程序都可能面临黑客、恶意软件和其他安全威胁。为此,开发者需要采取措施来保护应用程序的安全性能。本文将介绍 yii框架中的认证和授权功能,以帮助开发者保证其应用的安全性。 Yii框架的认证和授权功能 Yii框架提…

    用户投稿 2026年9月24日
    200
  • 抖音怎么下载视频?抖音怎么提取别人的视频

    抖音作为一个热门的短视频社交平台,凭借其多样化的短视频内容吸引了众多用户。部分用户在浏览抖音视频时,希望能将其保存下来以供后续观看。那么,如何在抖音上下载视频呢?接下来,本文将详细介绍几种下载抖音视频的方法以及相关的注意事项。 一、抖音视频下载方法 使用抖音官方提供的下载功能 抖音自身具备下载功能,…

    2026年9月24日
    400
  • google浏览器怎么把网页保存为PDF_google浏览器网页保存为PDF方法

    使用Chrome将网页保存为PDF,首先按Ctrl+P进入打印界面,选择“另存为PDF”并调整设置后保存;也可通过F12打开开发者工具,截取指定元素或完整页面截图后转为PDF;还可安装“Save as PDF”等扩展程序实现更高质量的导出。 如果您希望将当前浏览的网页完整保存以便离线查看或分享,Go…

    2026年9月24日
    000

发表回复

登录后才能评论
关注微信