当前位置: 首页 > 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.zskr.cn/news/1510000.html

相关文章:

  • 深度解析:精油代工 核心工艺与合规生产实践 - 资讯快报
  • 别再只盯着BIOS了!手把手教你用Port 60/64和ASL代码调试笔记本EC(Embedded Controller)
  • 2026 亳州卫生间漏水不用砸砖?微创补漏靠谱方案 - 苏易修缮
  • 2026年定制UPE超高分子量聚乙烯板材、耐磨pe聚乙烯板加工源头厂家对标指南 - 优质企业观察收录
  • 蓝桥杯真题保姆级解析:用BFS数岛屿,从地图边界海水搜索讲起
  • 长春手表回收避坑全攻略|劳力士/百达翡丽高价出手指南,2026二级市场行情+门店实测 - 天天生活分享日志
  • 拆解一个LM386芯片:用它的内部电路图,讲清楚集成功放设计的通用套路
  • 智能IDE试用期管理:节省90%重置时间的自动化解决方案
  • 2026南京黄金回收价格一览表 回收避坑与靠谱商家推荐 - 余生黄金回收
  • 时间序列分解实战:T-S-R原理、STL参数精调与业务归因
  • NYC Airbnb实战EDA:从数据清洗到业务落地的完整链路
  • 多模态理解到底谁更强:GPT-5.5 还是 Gemini 3.5?实测数据拆给你看
  • 2026海口市黄金回收全攻略 - 余生黄金回收
  • GitHub中文界面终极指南:3分钟告别英文困扰,开启高效开发之旅
  • AI多模型时代,开发者真正需要的是什么?一个聚合平台的选型实测
  • Unity 输入系统:新输入系统的手柄输入绑定与调试
  • 别再花钱买U盘了!用STM32F103C8T6的Flash自己做一个(CubeMX+USB MSC+FATFS)
  • 尼康高度计优质代理商推荐:时丰仪器,渠道正规适配多行业选型 - 品牌推荐大师
  • 告别CUDA魔改:用PyTorch原生DSVT Transformer高效处理3D点云(附代码)
  • 郑州殿堂级包包回收机构盘点:高端名包专属高价回收渠道 - 开心测评
  • 西宁城中区上门回收黄金,足不出户安心变现 - 上门黄金回收
  • 学生用SharePoint网课视频一键批量存本地(Electron桌面版,免服务器)
  • 2026最新贵阳黄金回收价格表避坑攻略与靠谱商家 - 余生黄金回收
  • 基于YOLOv11肺结节检测系统 医学图像诊断识别
  • 泉州市三菱重工空调维修师傅电话|各区金牌师傅,靠谱选欧米到家 - 欧米到家
  • 工业视觉实战:OpenCV检测PCB板定位孔圆心,附完整代码与参数调试心得
  • 2026重庆黄金回收硬实力榜单,收的顶稳居全能榜首断层领先 - 奢侈品回收测评
  • 镇江京口区金价888元每克 黄金上门回收服务正当时 - 上门黄金回收
  • 2026年贵阳全屋舒适系统选购完全指南:地暖、空调、新风、净水、空气能一站式解决方案 - 优质企业观察收录
  • JetBrains IDE试用期重置终极解决方案:ide-eval-resetter完整使用指南