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

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

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

2026/10/15 12:08:09 阅读更多 →
观察TaotokenTokenPlan套餐如何帮助初创团队控制AI成本

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

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

2026/10/15 12:08:16 阅读更多 →

最新新闻

AI代理“猜你有钱就推贵的”:基于购买力评估的推荐价格带动态调整策略

AI代理“猜你有钱就推贵的”:基于购买力评估的推荐价格带动态调整策略

先说个真实的感受。做推荐系统的人都知道,模型调来调去,无非是在“猜用户想要什么”上打转。但你有没有想过一个问题:系统会不会猜用户“买得起什么”?我最近复盘了一个实验项目,项目代号干脆就叫“模拟项目X”&#x…

2026/10/20 18:12:45 阅读更多 →
提示系统原型设计:从模糊需求到可落地方案

提示系统原型设计:从模糊需求到可落地方案

提示系统原型设计,听起来是件小事,画几个弹窗、排几个Toast,好像一两天就能搞定。但真正接过这类需求的架构师都明白,这玩意儿是需求模糊的重灾区。我见过太多项目,需求方开口就是“给个提示就行”“别太打扰用户”“重…

2026/10/20 18:12:45 阅读更多 →
金融科技AI岗位揭秘:视觉、语音、NLP、ML与AI安全都在做什么

金融科技AI岗位揭秘:视觉、语音、NLP、ML与AI安全都在做什么

视觉、语音、NLP、ML、AI安全:金融科技公司的这五个方向,分别都在做什么看到这个标题的时候,我愣了一下。不是因为方向多,而是因为五个方向同时出现这件事本身,恰好透露出金融科技行业对AI岗位的真实定义:这…

2026/10/20 18:12:45 阅读更多 →
Label Studio工程化指南:从画框工具到数据生产基础设施

Label Studio工程化指南:从画框工具到数据生产基础设施

用了两年多 Label Studio,我必须说一句:这可能是开源工具里被误解得最厉害的一个。很多人提到它,第一反应就是“标注工具”“画框软件”——拿来打几个检测框、标几段文本、分一下类,用完就关。我也曾经这么干过,直到某…

2026/10/20 18:12:45 阅读更多 →
Windows下多Tomcat实例部署:端口规划与服务注册实战

Windows下多Tomcat实例部署:端口规划与服务注册实战

1. 方案设计:多实例并存的关键不在安装,而在规划1.1 多Tomcat服务从什么需求场景来先交代我遇到的实际场景。一台Windows测试机上要同时部署三个Java Web应用:一个老系统还在用Tomcat 8,一个内部OA想跑在Tomcat 9上,还…

2026/10/20 18:12:45 阅读更多 →
大模型跨机房高可用 SLA 探针:基于 gRPC 双向流的微秒级心跳检测

大模型跨机房高可用 SLA 探针:基于 gRPC 双向流的微秒级心跳检测

在分布式大模型推理架构中,多机房、多云灾备已经成为保障业务 SLA(服务等级协议)的标配手段。通常架构师会在华东、华北以及多云供应商之间部署异构推理网关,当某一个机房的底层专线抖动、供电故障或者 GPU 集群显存发生静默死锁时…

2026/10/20 18:11:37 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →