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:08 阅读更多 →
【AI注意力机制底层逻辑】:20年架构师亲授,3步看懂Transformer核心奥秘

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

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

2026/7/25 17:00:08 阅读更多 →
观察TaotokenTokenPlan套餐如何帮助初创团队控制AI成本

观察TaotokenTokenPlan套餐如何帮助初创团队控制AI成本

观察Taotoken TokenPlan套餐如何帮助初创团队控制AI成本 对于初创团队和独立开发者而言,在拥抱大模型能力的同时,如何有效管理并预测AI调用成本,是一个直接影响项目可持续性的现实问题。按次计费虽然灵活,但面对业务量的波动&…

2026/7/25 16:59:08 阅读更多 →

最新新闻

taotoken用量看板如何帮助开发者优化模型选型与预算

taotoken用量看板如何帮助开发者优化模型选型与预算

Taotoken用量看板如何帮助开发者优化模型选型与预算 1. 从模糊调用到清晰感知 在接入多个大模型进行开发时,很多开发者最初的状态是“黑盒”调用。我们通过统一的API发送请求,收到响应,但背后具体消耗了多少资源、不同模型在处理同类任务时…

2026/7/25 17:12:14 阅读更多 →
Unity游戏开发中手写EventBus:从原理到实现,彻底解决模块耦合问题

Unity游戏开发中手写EventBus:从原理到实现,彻底解决模块耦合问题

1. 项目概述:为什么我们需要一个自己的EventBus? 在Unity项目里摸爬滚打几年,你肯定遇到过这样的场景:一个UI按钮点击,需要触发角色移动、播放音效、更新任务进度,还要通知其他系统。新手最常见的做法是什么…

2026/7/25 17:12:14 阅读更多 →
AI图像批处理系统在艺术数字化中的应用与实践

AI图像批处理系统在艺术数字化中的应用与实践

1. 项目背景与核心价值去年接手一个文创企业的设计需求时,我深刻体会到批量处理图片的痛点。客户需要将300多张传统水墨画扫描件统一调整尺寸、增强细节并添加数字水印,手动操作几乎耗掉整个团队两天时间。正是这次经历促使我开发了"柳墨丹青"…

2026/7/25 17:12:14 阅读更多 →
2024-2025 AI Agent开发实战:从核心概念到工程化部署完整指南

2024-2025 AI Agent开发实战:从核心概念到工程化部署完整指南

如果你在2024年关注AI开发,一定听过“AI Agent”这个词。它不再是实验室里的概念,而是正在成为解决复杂任务、提升开发效率的下一代工具。但问题来了:面对铺天盖地的教程、框架和概念,一个开发者,尤其是刚入门的开发者…

2026/7/25 17:12:14 阅读更多 →
Linux管理员的核心思维:从原理到实践的运维方法论

Linux管理员的核心思维:从原理到实践的运维方法论

1. 为什么Linux管理员的思维方式比命令熟练度更重要 刚入行时,我总以为Linux管理员的核心竞争力是记住各种命令参数和快捷键。直到有次面试,面试官让我解决一个实际的生产环境问题,才发现自己引以为傲的命令记忆在真实场景中完全不够用。那次…

2026/7/25 17:12:14 阅读更多 →
对比官方价Taotoken的Token Plan套餐究竟能省多少

对比官方价Taotoken的Token Plan套餐究竟能省多少

对比官方价,Taotoken的Token Plan套餐究竟能省多少 在构建和迭代AI应用时,独立开发者除了关注模型能力,成本控制也是一个绕不开的核心议题。直接接入各大模型厂商的官方API,虽然直接,但面对多个平台、不同计价单位以及…

2026/7/25 17:11:13 阅读更多 →

日新闻

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

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

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

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

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

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

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

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

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

2026/7/25 0:00:35 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/7/24 18:52:18 阅读更多 →

月新闻