C++ 中 weak ptr 详解:打破循环引用的观察者指针
C 中 weak_ptr 详解打破循环引用的观察者指针一、引言不参与所有权的智能指针std::weak_ptr是 C11 引入的智能指针它是std::shared_ptr的配套工具。与shared_ptr不同weak_ptr不参与对象的共享所有权——它持有一个对象的“弱引用”不会增加对象的引用计数不会阻止对象被销毁。weak_ptr的核心使命是打破shared_ptr之间的循环引用以及提供安全的临时访问——在需要时尝试提升为shared_ptr如果对象还存在就安全访问如果已经被销毁就安全失败。它就像一个可以“过期”的观察者。二、核心概念速览| 维度 | 说明 || --- | --- || 所有权 | 无所有权不参与引用计数 || 引用计数影响 | 不影响强引用计数只影响弱引用计数 || 能否直接访问对象 | 不能没有 operator* 和 operator- || 访问方式 | 先 lock() 提升为 shared_ptr检查是否为空 || 主要用途 | 打破循环引用、观察者模式、缓存系统 || 过期检测 | 当所有 shared_ptr 销毁后weak_ptr 自动“过期” || 创建方式 | 从 shared_ptr 或另一个 weak_ptr 构造 || 线程安全 | 与 shared_ptr 相同引用计数操作原子 |三、weak_ptr 的内部原理3.1 weak_ptr 与控制块的关系weak_ptr和shared_ptr共享同一个控制块(Control Block)。控制块中维护两个引用计数shared_count强引用计数shared_ptr的数量决定对象何时被销毁weak_count弱引用计数weak_ptr的数量加上shared_ptr的数量决定控制块何时被销毁图表代码下载全屏3.2 对象与控制块的销毁时机图表代码下载全屏关键规则对象在shared_count 0时销毁控制块在shared_count 0 weak_count 0时销毁weak_ptr不阻止对象销毁但可能延长控制块的生命周期3.3 weak_ptr 的简化实现cpp复制下载templatetypename T class weak_ptr { private: T* ptr_; // 指向被管理的对象可能过期 control_block* cb_; // 指向控制块 public: // 从 shared_ptr 构造 weak_ptr(const shared_ptrT sp) : ptr_(sp.ptr_), cb_(sp.cb_) { cb_-weak_count; // 增加弱引用计数 } // 拷贝构造 weak_ptr(const weak_ptr other) : ptr_(other.ptr_), cb_(other.cb_) { cb_-weak_count; } // 析构函数 ~weak_ptr() { --cb_-weak_count; // 如果 shared_count 和 weak_count 都归零释放控制块 if (cb_-shared_count 0 cb_-weak_count 0) { delete cb_; } } // 尝试提升为 shared_ptr shared_ptrT lock() const { // 原子地检查并增加 shared_count if (cb_-shared_count 0) { cb_-shared_count; // 原子操作 return shared_ptrT(ptr_, cb_); } return shared_ptrT(); // 对象已销毁返回空 shared_ptr } // 检查对象是否已被销毁 bool expired() const { return cb_-shared_count 0; } // 返回引用计数调试用 size_t use_count() const { return cb_-shared_count; } };四、weak_ptr 的核心操作4.1 基本使用cpp复制下载#include memory #include iostream int main() { // 创建 shared_ptr auto sp std::make_sharedint(42); std::cout use_count: sp.use_count() std::endl; // 1 // 从 shared_ptr 创建 weak_ptr std::weak_ptrint wp sp; std::cout use_count: sp.use_count() std::endl; // 1不变 std::cout expired: wp.expired() std::endl; // false // lock()尝试提升为 shared_ptr if (auto locked wp.lock()) { std::cout Value: *locked std::endl; // 42 std::cout use_count: sp.use_count() std::endl; // 2 } // locked 析构use_count 回到 1 // 重置 shared_ptr对象被销毁 sp.reset(); std::cout expired: wp.expired() std::endl; // true // lock() 返回空指针 if (auto locked wp.lock()) { // 不会执行到这里 } else { std::cout Object has been destroyed std::endl; } }4.2 weak_ptr 的典型使用模式cpp复制下载// 模式一安全访问检查是否过期 void safeAccess(std::weak_ptrint wp) { if (auto sp wp.lock()) { // 对象仍然存在安全使用 *sp 100; } else { // 对象已被销毁执行降级逻辑 std::cout Object no longer available std::endl; } } // 模式二过期检测 void checkExpired(std::weak_ptrint wp) { if (wp.expired()) { std::cout Expired std::endl; } else { // 注意这里对象可能即将被销毁 // 应该立即 lock() 而不是先检查再使用 } }五、weak_ptr 的主要应用场景5.1 场景一打破循环引用最常用这是weak_ptr存在的首要原因。当两个对象通过shared_ptr互相引用时会形成循环引用导致内存泄漏。cpp复制下载#include memory #include iostream #include string class Person { public: std::string name_; std::shared_ptrPerson bestFriend_; // 强引用 // 如果两个 Person 互相持有 shared_ptr → 循环引用 → 内存泄漏 Person(const std::string name) : name_(name) { std::cout name_ created std::endl; } ~Person() { std::cout name_ destroyed std::endl; } }; // 正确的设计一方使用 weak_ptr class PersonFixed { public: std::string name_; std::weak_ptrPersonFixed bestFriend_; // 弱引用不增加引用计数 PersonFixed(const std::string name) : name_(name) { std::cout name_ created std::endl; } ~PersonFixed() { std::cout name_ destroyed std::endl; } void greetFriend() { if (auto friendPtr bestFriend_.lock()) { std::cout name_ : Hello, friendPtr-name_ ! std::endl; } else { std::cout name_ : My friend is gone... std::endl; } } }; int main() { { auto alice std::make_sharedPersonFixed(Alice); auto bob std::make_sharedPersonFixed(Bob); alice-bestFriend_ bob; // Alice 弱引用 Bob bob-bestFriend_ alice; // Bob 弱引用 Alice alice-greetFriend(); } // alice 和 bob 正确销毁 // 输出: Alice destroyed, Bob destroyed }图表代码下载全屏5.2 场景二观察者模式cpp复制下载#include memory #include vector #include iostream #include algorithm // 主题Subject class Subject { public: void notifyObservers() { // 清理过期的观察者并通知有效的观察者 observers_.erase( std::remove_if(observers_.begin(), observers_.end(), [](const std::weak_ptrObserver wp) { return wp.expired(); }), observers_.end() ); for (auto weak : observers_) { if (auto observer weak.lock()) { observer-onNotify(); } } } void addObserver(std::shared_ptrObserver observer) { observers_.push_back(observer); // 存储为 weak_ptr } private: std::vectorstd::weak_ptrObserver observers_; }; // 观察者Observer class Observer { public: virtual void onNotify() 0; virtual ~Observer() default; }; class ConcreteObserver : public Observer { std::string name_; public: ConcreteObserver(const std::string name) : name_(name) { } void onNotify() override { std::cout name_ received notification std::endl; } ~ConcreteObserver() { std::cout name_ destroyed std::endl; } }; int main() { Subject subject; { auto obs1 std::make_sharedConcreteObserver(Observer1); auto obs2 std::make_sharedConcreteObserver(Observer2); subject.addObserver(obs1); subject.addObserver(obs2); subject.notifyObservers(); // 两个观察者都收到通知 } // obs2 离开作用域被销毁 subject.notifyObservers(); // 只有 Observer1 收到通知 // Observer2 的 weak_ptr 已过期被自动清理 }5.3 场景三缓存系统cpp复制下载#include memory #include unordered_map #include string #include iostream #include algorithm class Image { public: std::string filename_; std::vectorchar data_; // 大量的图像数据 Image(const std::string filename) : filename_(filename) { std::cout Loading image: filename std::endl; data_.resize(10 * 1024 * 1024); // 模拟 10MB 图像 } ~Image() { std::cout Unloading image: filename_ std::endl; } }; class ImageCache { std::unordered_mapstd::string, std::weak_ptrImage cache_; public: // 获取图像如果缓存中有返回否则加载并缓存 std::shared_ptrImage getImage(const std::string filename) { auto it cache_.find(filename); // 检查缓存是否命中且未过期 if (it ! cache_.end()) { if (auto img it-second.lock()) { std::cout Cache hit: filename std::endl; return img; } // 已过期移除条目 cache_.erase(it); } // 缓存未命中加载新图像 auto img std::make_sharedImage(filename); cache_[filename] img; // 存储为 weak_ptr return img; } // 清理过期的缓存条目 void cleanExpired() { for (auto it cache_.begin(); it ! cache_.end(); ) { if (it-second.expired()) { it cache_.erase(it); } else { it; } } } size_t cacheSize() const { size_t count 0; for (const auto entry : cache_) { if (!entry.second.expired()) { count; } } return count; } }; int main() { ImageCache cache; { auto img1 cache.getImage(photo1.jpg); // 加载并缓存 auto img2 cache.getImage(photo2.jpg); // 加载并缓存 std::cout Active images in cache: cache.cacheSize() std::endl; // 2 auto img1Again cache.getImage(photo1.jpg); // 缓存命中 } // img1 和 img2 离开作用域引用计数归零 std::cout Active images in cache: cache.cacheSize() std::endl; // 0 // 缓存的 weak_ptr 已过期不会阻止 Image 对象被释放 // 应用程序可以主动释放不用的图像缓存自动感知过期 }5.4 场景四防止 this 循环引用cpp复制下载#include memory #include functional #include iostream class AsyncTask : public std::enable_shared_from_thisAsyncTask { public: void startAsync() { // 捕获 weak_ptr 而不是 shared_ptr避免循环引用 std::weak_ptrAsyncTask weakSelf shared_from_this(); // 模拟异步操作 scheduleCallback([weakSelf]() { if (auto self weakSelf.lock()) { self-onCompleted(); } else { std::cout Task was cancelled std::endl; } }); } void onCompleted() { std::cout Task completed std::endl; } private: void scheduleCallback(std::functionvoid() callback) { // 模拟异步调度 callback(); } }; int main() { auto task std::make_sharedAsyncTask(); task-startAsync(); // 如果 task 被提前释放回调能安全检测到 }六、weak_ptr 的注意事项6.1 lock() 和 expired() 的竞态条件cpp复制下载// ❌ 不安全的做法 void unsafeAccess(std::weak_ptrint wp) { if (!wp.expired()) { // 检查时未过期 auto sp wp.lock(); // 但这里可能已经过期了 // 多线程环境下expired() 和 lock() 之间对象可能被销毁 } } // ✓ 安全的做法直接 lock() void safeAccess(std::weak_ptrint wp) { if (auto sp wp.lock()) { // 原子地尝试提升 // sp 持有强引用保证对象在作用域内不被销毁 *sp 100; } }6.2 make_shared 与 weak_ptr 的内存释放cpp复制下载// make_shared 分配单块内存对象 控制块 auto sp std::make_sharedint(42); std::weak_ptrint wp sp; sp.reset(); // shared_count 0对象析构 // 但内存块没有被释放因为 weak_count 0wp 仍存在 // wp 存在期间整个内存块包括已析构对象占用的空间不会释放 // 直到 wp 也销毁weak_count 0内存块才被释放 // 如果对象很大且 weak_ptr 长期存在考虑用直接构造 std::shared_ptrint sp2(new int(42)); // 分开分配 std::weak_ptrint wp2 sp2; sp2.reset(); // 对象内存立即释放只有控制块保留七、总结std::weak_ptr是 C 智能指针体系中专门解决共享所有权边界问题的工具本质weak_ptr是shared_ptr的“弱观察者”——它引用控制块但不增加强引用计数不阻止对象销毁只延长控制块的生命周期。当shared_count 0时对象销毁当shared_count 0 weak_count 0时控制块销毁。访问方式weak_ptr不能直接解引用没有operator*和operator-必须通过lock()提升为shared_ptr才能访问对象。lock()返回空指针表示对象已被销毁。主要场景打破循环引用父子结构、双向关联中一方使用weak_ptr观察者模式Subject 持有 Observer 的weak_ptrObserver 可独立销毁缓存系统缓存持有weak_ptr应用层可主动释放不用的对象异步回调在回调中捕获weak_ptr安全检测对象是否仍存在注意事项不要先调用expired()再调用lock()直接lock()后判空make_shared的单块内存分配在weak_ptr存在时不会释放weak_ptr不是线程安全的银弹——它只保证引用计数操作的原子性weak_ptr的设计体现了 C 资源管理的精细控制——它不拥有资源但能安全地观察资源的生命周期。正确使用它可以优雅地解决共享所有权模型中的循环引用和对象生命周期不确定性这两大难题。

相关新闻

LTX-2.3 V1.6:基于int8量化的低显存AI视频生成工具实践

LTX-2.3 V1.6:基于int8量化的低显存AI视频生成工具实践

如果你正在寻找一个能够将文字或图片快速转换成带音频视频的AI工具,而且希望它既不需要复杂的环境搭建,又能在普通消费级显卡上流畅运行,那么LTX-2.3工具V1.6版本值得你重点关注。传统视频生成方案往往需要高端显卡和繁琐的配置流程,而这个工具通过int8量化技术实现了2-4倍…

2026/7/25 17:00:14阅读更多 →
【AI注意力机制底层逻辑】:20年架构师亲授,3步看懂Transformer核心奥秘

【AI注意力机制底层逻辑】:20年架构师亲授,3步看懂Transformer核心奥秘

更多请点击: https://intelliparadigm.com 第一章:什么是注意力机制——从人类认知到AI建模 注意力机制并非深度学习的发明,而是对人类感知与认知过程的数学抽象。当我们阅读一段文字时,并非均匀处理每个字;相反&…

2026/7/25 17:00:14阅读更多 →
YOLOv8在食品包装喷码识别中的优化实践

YOLOv8在食品包装喷码识别中的优化实践

1. 项目背景与痛点分析在食品包装生产线上,生产日期喷码识别是质量管控的关键环节。传统人工检测方式存在效率低(每分钟仅能检测20-30个包装)、漏检率高(约5%-8%)等问题。更棘手的是,喷码模糊问题在高速生产…

2026/7/25 16:58:14阅读更多 →
VTable-Sheet:重新定义Web电子表格的开源解决方案

VTable-Sheet:重新定义Web电子表格的开源解决方案

VTable-Sheet:重新定义Web电子表格的开源解决方案 引言:为什么需要VTable-Sheet?在Web应用开发中,电子表格功能一直是一个难点。传统方案要么依赖Excel Online这样的重量级产品,要么使用基础HTML表格,缺乏…

2026/7/25 18:26:24阅读更多 →
GXDE OS:基于Debian的经典DDE 15桌面Linux发行版体验与部署指南

GXDE OS:基于Debian的经典DDE 15桌面Linux发行版体验与部署指南

这次我们来看一个名为 GXDE OS 的 Linux 发行版。它不是一个新模型或 AI 工具,而是一个基于 Debian 的桌面操作系统,其核心卖点是“开箱即用”和“经典设计”。简单来说,GXDE OS 的目标是提供一个既美观、轻量,又预装了丰富实用软件的 Linux 环境,尤其引人注目的是它复活了…

2026/7/25 18:26:24阅读更多 →
AI智能内容处理流水线:大模型与多模态技术实践

AI智能内容处理流水线:大模型与多模态技术实践

1. 项目背景与核心价值这个实训项目聚焦于当前AI领域最前沿的三个技术方向:大模型接入、多模态分析和视频检测。这三个技术模块的组合,实际上构建了一个完整的智能内容处理流水线。在真实业务场景中,这样的技术栈可以支撑从原始数据输入到智能…

2026/7/25 18:26:24阅读更多 →
AI图片审核系统在民宿行业的应用与实践

AI图片审核系统在民宿行业的应用与实践

1. 项目背景与行业痛点民宿行业长期存在"照骗"问题——房东上传经过过度修饰的房源照片,导致实际入住体验与预期严重不符。根据行业调研数据,约37%的住客投诉直接与房源图片失真有关。这种信息不对称不仅损害消费者权益,更会引发平…

2026/7/25 18:26:24阅读更多 →
YouDiscoveredt

YouDiscoveredt

YouDiscoveredt:从入门到精通的完整指南 引言:什么是 YouDiscoveredt?YouDiscoveredt 是一个虚构的、用于演示编程概念的综合框架。它的核心思想是“发现即掌握”——通过逐步探索和构建,让开发者从零开始理解复杂系统。本文将通过…

2026/7/25 18:26:24阅读更多 →
对比学习中的InfoNCE Loss与互信息关系解析

对比学习中的InfoNCE Loss与互信息关系解析

1. 对比学习与InfoNCE Loss基础解析 对比学习(Contrastive Learning)作为自监督学习的重要分支,近年来在计算机视觉、自然语言处理等领域展现出强大的特征提取能力。其核心思想是通过构造正负样本对,让模型学会区分相似与不相似的…

2026/7/25 18:24:24阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/25 1:01:14阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/25 1:01:14阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/25 1:01:14阅读更多 →
突破文档下载限制:kill-doc让你看到的都能保存

突破文档下载限制:kill-doc让你看到的都能保存

突破文档下载限制:kill-doc让你看到的都能保存 【免费下载链接】kill-doc 看到经常有小伙伴们需要下载一些免费文档,但是相关网站浏览体验不好各种广告,各种登录验证,需要很多步骤才能下载文档,该脚本就是为了解决您的…

2026/7/25 0:01:16阅读更多 →
C++ string类模拟实现:从深拷贝到内存管理的完整指南

C++ string类模拟实现:从深拷贝到内存管理的完整指南

1. 项目概述:为什么我们要“手撕”string类?在C的学习道路上,尤其是从C语言过渡到C的“初阶”阶段,string类绝对是一个绕不开的核心。标准库里的std::string用起来太方便了,、find、substr,几个操作符和函数…

2026/7/25 0:01:16阅读更多 →
三角洲寻宝鼠工具:高效文件搜索与资源管理实战指南

三角洲寻宝鼠工具:高效文件搜索与资源管理实战指南

1. 先搞清楚“三角洲寻宝鼠”到底是什么工具从名称来看,“三角洲寻宝鼠”更像是一个资源查找或文件检索类工具,而不是游戏或娱乐软件。这类工具的核心价值在于帮助用户快速定位特定资源,比如文档、图片、压缩包或特定格式的文件。如果你经常需…

2026/7/25 0:01:16阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/24 23:01:03阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/24 19:00:40阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/24 19:00:40阅读更多 →