当前位置: 首页 > news >正文

C++ 智能指针完全指南(三):weak_ptr 与循环引用

引言

上一篇我们学习了shared_ptr——通过引用计数实现共享所有权。但引用计数有一个致命缺陷:循环引用

两个对象互相持有对方的shared_ptr,引用计数永远无法归零——即使你已经无法访问它们,它们仍然互相"拽着"对方,谁也释放不了。这就是内存泄漏

weak_ptr就是为解决这个问题而生的。它指向shared_ptr管理的对象,但不增加引用计数。当最后一个shared_ptr销毁时,对象照常释放,weak_ptr会自动变为空。

第一部分:循环引用的灾难

一、一个内存泄漏的实例

#include <memory> #include <iostream> using namespace std; class B; // 前向声明 class A { public: shared_ptr<B> ptrB; ~A() { cout << "A 析构" << endl; } }; class B { public: shared_ptr<A> ptrA; // ← 如果是 shared_ptr,循环引用! ~B() { cout << "B 析构" << endl; } }; int main() { auto a = make_shared<A>(); auto b = make_shared<B>(); a->ptrB = b; // a 持有 b b->ptrA = a; // b 持有 a → 循环! // a 和 b 离开作用域 // 但它们的引用计数各为 1(对方持有的) // → 永远不会析构 → 内存泄漏! }

运行结果:什么也不输出。A 和 B 的析构函数永远不会被调用

二、循环引用的形成过程

第二部分:weak_ptr 的原理

一、weak_ptr 是什么

weak_ptr是一种不控制对象生命周期的智能指针。它指向shared_ptr管理的对象,但:

  • 不增加引用计数

  • 不影响对象的释放时机

  • 当对象被释放后,weak_ptr自动变为空(不会悬空)

二、基本操作

auto sp = make_shared<int>(42); weak_ptr<int> wp = sp; // 从 shared_ptr 创建 // 检查对象是否还存在 if (!wp.expired()) { cout << "对象还活着" << endl; } // 获取 shared_ptr(锁定) if (auto locked = wp.lock()) { // locked 是一个 shared_ptr,引用计数 +1 cout << *locked << endl; } // locked 离开作用域,引用计数 -1 sp.reset(); // 释放对象 if (wp.expired()) { cout << "对象已被释放" << endl; } // 对象已释放,lock() 返回空 shared_ptr auto locked = wp.lock(); if (!locked) { cout << "lock() 返回空" << endl; }
操作含义
wp.expired()检查对象是否已被释放
wp.lock()返回一个shared_ptr(如果对象还活着)
wp.use_count()返回shared_ptr的引用计数
wp.reset()清空weak_ptr

三、为什么 lock() 是原子操作

// ❌ 不是线程安全的 if (!wp.expired()) { // 步骤1:检查 auto sp = wp.lock(); // 步骤2:锁定 // 步骤1 和 步骤2 之间,对象可能被另一个线程释放! } // ✅ lock() 是原子操作 if (auto sp = wp.lock()) { // 一步完成 // 安全使用 sp }

第三部分:解决循环引用

class B; class A { public: shared_ptr<B> ptrB; // A 持有 B(强引用) ~A() { cout << "A 析构" << endl; } }; class B { public: weak_ptr<A> ptrA; // B 持有 A(弱引用!不增加计数) ~B() { cout << "B 析构" << endl; } }; int main() { auto a = make_shared<A>(); auto b = make_shared<B>(); a->ptrB = b; // a 持有 b → B 引用计数 = 2 b->ptrA = a; // b 持有 a → A 引用计数 = 1(weak_ptr 不增加!) // main 结束,a 和 b 销毁: // a 销毁 → A 引用计数 = 0 → A 析构 // → A 析构后,A::ptrB 销毁 → B 引用计数 -1 // b 销毁 → B 引用计数 = 0 → B 析构 ✅ }

运行结果

A 析构
B 析构

为什么能解决?

第四部分:weak_ptr 的使用场景

一、观察者模式

class Subject { private: vector<weak_ptr<Observer>> observers; // 弱引用观察者 public: void addObserver(shared_ptr<Observer> obs) { observers.push_back(obs); } void notify() { for (auto& weak : observers) { if (auto obs = weak.lock()) { // 观察者还活着 obs->update(); } else { // 观察者已销毁,可以清理 weak_ptr } } } }; // 观察者释放后,Subject 不会阻止其销毁

二、缓存系统

class Cache { private: map<int, weak_ptr<Data>> cache; public: shared_ptr<Data> get(int key) { auto it = cache.find(key); if (it != cache.end()) { if (auto data = it->second.lock()) { return data; // 缓存命中 } // 数据已被外部释放,清理缓存 cache.erase(it); } auto data = make_shared<Data>(key); cache[key] = data; return data; } }; // 缓存持有 weak_ptr,外部用完了数据自然释放

三、打破 shared_ptr 循环引用

第五部分:三种智能指针总结

对比unique_ptrshared_ptrweak_ptr
所有权独占共享不拥有(弱引用)
拷贝✅(计数+1)✅(不增加计数)
移动
大小8 字节16 字节16 字节
释放自动计数归零自动不影响释放
循环引用不存在此问题会内存泄漏解决循环引用
创建make_uniquemake_shared从 shared_ptr 创建

使用原则

总结

一、核心要点

要点内容
本质弱引用,不增加引用计数,不控制对象生命周期
核心操作lock()获取shared_ptr(原子操作)、expired()检查对象是否存活
主要用途打破shared_ptr循环引用、实现观察者模式、缓存系统
创建shared_ptr创建:weak_ptr<T> wp = sp;

二、一句话记忆

weak_ptrshared_ptr的弱引用搭档,指向对象但不增加引用计数。用lock()原子地获取一个shared_ptr来安全访问对象。主要用来打破循环引用——父子关系中父用shared_ptr、子用weak_ptr

http://www.jsqmd.com/news/998228/

相关文章:

  • 开源、网页端、集成式小分子质谱鉴定
  • 抖音下载终极指南:免费无水印批量下载完整教程
  • 2026 防城港厨卫屋面地下室漏水瓷砖空鼓测评:吉修匠 99.8 分五星榜首 - 吉修匠
  • 从LTE到5G:CORESET设计如何解决老网络的‘控制信道之痛’?
  • 2026 亳州卫生间漏水不用砸砖?微创补漏靠谱方案 - 苏易修缮
  • 2026年定制UPE超高分子量聚乙烯板材、耐磨pe聚乙烯板加工源头厂家对标指南 - 优质企业观察收录
  • 蓝桥杯真题保姆级解析:用BFS数岛屿,从地图边界海水搜索讲起
  • 广州园区标识标牌定制常见问题解答(2026专家版) - 资讯快报
  • P87LPC761深度解析:16引脚80C51 MCU的低功耗设计与实战避坑指南
  • 从‘听不清’到‘听得清’:聊聊那些藏在微信语音、Teams会议里的音频3A算法
  • 为你的DIY小音箱选对管:OCL功放晶体管(三极管)选型与散热设计全攻略
  • 实测!青岛那些年一起吃串的地方,老牌连锁海鲜烧烤高性价比
  • 高效电商自动化实战:深度解析京东抢购框架JDspyder
  • ARM Cortex-M异常处理实战:当你的MCU卡在HardFault,如何通过UFSR的INVPC位揪出“无效PC”这个元凶
  • 长春手表回收避坑全攻略|劳力士/百达翡丽高价出手指南,2026二级市场行情+门店实测 - 天天生活分享日志
  • 油皮防晒怎么选?2026夏季防晒霜测评指南,主打长效清爽控油不闷肤 - 博客万
  • 2026杭州劳力士回收深度攻略:行情走势、避坑细则、品牌梯队全解析 - 薛定谔的梨花猫
  • 2026年郑州空压机余热回收选型指南:从能耗黑洞到年省电费20万的实战路线 - 优质企业观察收录
  • 客服岗位未来最吃香的能力是智能知识库管理
  • Halcon实战:别再手动连轮廓了!union_straight_contours_xld参数详解与避坑指南
  • 智能IDE试用期管理:节省90%重置时间的自动化解决方案
  • 拆解一个LM386芯片:用它的内部电路图,讲清楚集成功放设计的通用套路
  • 实测青岛老牌网红烧烤店!那些年一起吃串的地方,高性价比聚餐首选
  • 告别NeRF的‘过平滑’:手把手教你用PyTorch复现Instant-NGP的哈希编码层
  • 如何快速掌握ComfyUI-Manager:AI绘画工具管理的终极指南 [特殊字符]
  • 2026实测!视频号视频怎么下载到相册?苹果安卓保存方法区别 - 科技热点发布
  • 2026南京黄金回收价格一览表 回收避坑与靠谱商家推荐 - 余生黄金回收
  • Python面试翻车?别怪面试官狠,只怪你没搞懂这3个致命坑
  • 2026三明黄金回收全攻略 实体门店评测及避坑指南 - 余生黄金回收
  • 2026普洱市黄金回收全攻略 实体门店评测及避坑指南 - 余生黄金回收