讲师中心 微信公众号

工具推荐

视频效率加速
剧云
剧云

中文短剧剧本制作平台

Laper
Laper

剧本策划到成片协作

立刻MV
立刻MV

低成本歌曲 MV、视频生成

updream

一键生成高质量推广成片

C++weak_ptr实现对象生命周期监控

风晨同学_6258

风晨同学_6258

发布时间:2025-09-04 11:43:24

|

647人浏览过

|

来源于php中文网

原创

weak_ptr通过lock()方法监控对象生命周期,解决循环引用和内存泄漏问题,在缓存、观察者模式、父子结构等场景中实现安全的非拥有性引用,确保对象销毁后访问可控。

c++weak_ptr实现对象生命周期监控

C++ weak_ptr
通过提供一种非拥有性引用,允许我们观察由
shared_ptr
管理的对象生命周期。当所有
shared_ptr
实例都销毁,对象被释放后,
weak_ptr
会“感知”到这一点,并在尝试访问时返回空,从而实现对对象存活状态的监控。这在需要打破循环引用或避免悬空指针风险的场景下,简直是设计模式中的一把利器。

解决方案

要实现

C++ weak_ptr
的对象生命周期监控,核心在于理解
weak_ptr
与
shared_ptr
之间的协作机制。
weak_ptr
本身不参与对象的引用计数,它只是一个“旁观者”。当你从一个
shared_ptr
创建一个
weak_ptr
时,它会指向同一个控制块,但不会增加引用计数。这意味着,即使有多个
weak_ptr
指向一个对象,只要所有
shared_ptr
都消失了,对象就会被销毁。

监控的关键在于

weak_ptr::lock()
方法。当你想要安全地访问
weak_ptr
所指向的对象时,应该调用
lock()
。这个方法会尝试创建一个
shared_ptr
。如果对象仍然存活(即至少有一个
shared_ptr
仍在管理它),
lock()
就会成功返回一个有效的
shared_ptr
,你可以通过这个
shared_ptr
安全地访问对象。如果对象已经被销毁(所有
shared_ptr
都已失效),
lock()
会返回一个空的
shared_ptr
。通过检查
lock()
返回的
shared_ptr
是否为空,我们就能准确判断对象是否还“活着”。

#include <iostream>
#include <memory>
#include <string>

class MyObject {
public:
    std::string name;
    MyObject(const std::string& n) : name(n) {
        std::cout << "MyObject " << name << " created." << std::endl;
    }
    ~MyObject() {
        std::cout << "MyObject " << name << " destroyed." << std::endl;
    }
    void doSomething() {
        std::cout << "MyObject " << name << " is doing something." << std::endl;
    }
};

void monitor_object(std::weak_ptr<MyObject> weak_obj) {
    if (auto shared_obj = weak_obj.lock()) {
        std::cout << "  [Monitor] Object " << shared_obj->name << " is still alive." << std::endl;
        shared_obj->doSomething();
    } else {
        std::cout << "  [Monitor] Object has been destroyed." << std::endl;
    }
}

int main() {
    std::cout << "--- Phase 1: Object is alive ---" << std::endl;
    std::shared_ptr<MyObject> obj_ptr = std::make_shared<MyObject>("DataA");
    std::weak_ptr<MyObject> weak_obj_ptr = obj_ptr;

    monitor_object(weak_obj_ptr); // 此时对象存活

    std::cout << "\n--- Phase 2: Reset shared_ptr ---" << std::endl;
    obj_ptr.reset(); // 释放shared_ptr,对象被销毁

    monitor_object(weak_obj_ptr); // 此时对象已销毁

    std::cout << "\n--- Phase 3: Another object ---" << std::endl;
    {
        auto another_obj_ptr = std::make_shared<MyObject>("DataB");
        std::weak_ptr<MyObject> another_weak_ptr = another_obj_ptr;
        monitor_object(another_weak_ptr);
    } // another_obj_ptr 在这里离开作用域,对象被销毁
    monitor_object(std::weak_ptr<MyObject>(nullptr)); // 传递一个空的weak_ptr

    return 0;
}

这段代码清晰地展示了

weak_ptr
如何通过
lock()
方法来判断并安全访问一个对象。当
shared_ptr
还存在时,
lock()
能获取到有效的
shared_ptr
;一旦
shared_ptr
消失,对象被销毁,
lock()
就会返回空。

立即学习“C++免费学习笔记(深入)”;

为什么
weak_ptr
能有效避免内存泄漏?

说白了,

weak_ptr
在避免内存泄漏方面的作用,主要体现在它能够漂亮地解决“循环引用”这个老大难问题。我们都知道,
shared_ptr
通过引用计数来管理对象的生命周期。当所有指向某个对象的
shared_ptr
都销毁了,引用计数归零,对象自然就被释放了。但想象一下,如果对象A持有一个指向对象B的
shared_ptr
,同时对象B又持有一个指向对象A的
shared_ptr
,这就像两个人互相抓着对方的衣领,谁也不肯放手。

在这种情况下,即使外部已经没有任何

shared_ptr
指向A或B,它们的内部引用计数却永远无法降到零。因为A的
shared_ptr
持有B,B的
shared_ptr
持有A,它们各自的引用计数至少为1。结果就是,A和B都无法被销毁,它们占用的内存也就永远无法释放,这就造成了经典的内存泄漏。

weak_ptr
的出现,就是为了打破这种僵局。它提供了一种“观察者”的角色,它指向对象,但不增加对象的引用计数。如果我们把循环引用中的一个
shared_ptr
替换成
weak_ptr
,比如让对象A持有对象B的
shared_ptr
,而对象B持有对象A的
weak_ptr
。这样一来,A的生命周期依然由B的
shared_ptr
影响(如果B是唯一的拥有者),但A的生命周期不再反过来影响B。当外部对A的
shared_ptr
全部失效时,A的引用计数归零,A就会被销毁。A销毁后,B持有的那个
weak_ptr
自然也就“过期”了,B就能感知到A已经不在了。这样一来,循环的链条就被斩断,两个对象都能在适当的时候被正确销毁,避免了内存泄漏。我个人觉得,这简直是处理复杂对象图关系时的救星。

在哪些实际场景中,
weak_ptr
的生命周期监控功能至关重要?

weak_ptr
的生命周期监控能力,在很多实际的、复杂的C++应用中都扮演着不可或缺的角色。它不仅仅是解决循环引用的工具,更是构建健壮、灵活系统的重要基石。

首先,缓存系统。想象一个图片缓存或者数据缓存,它存储了大量可能被程序其他部分使用的对象。如果缓存直接持有这些对象的

shared_ptr
,那么即使其他所有地方都不再使用某个图片,缓存也会因为持有
shared_ptr
而阻止该图片被释放。这会导致缓存无限增长,最终耗尽内存。正确的做法是,缓存应该持有
weak_ptr
。当其他部分不再需要某个图片时,它的
shared_ptr
就会被释放,图片对象自然销毁。缓存下次尝试访问时,通过
weak_ptr::lock()
发现图片已不存在,便可以安全地将其从缓存中移除,避免了存储已失效数据的开销。

其次,观察者模式或事件系统。在UI编程或事件驱动架构中,一个“主题”(Subject)可能需要通知多个“观察者”(Observer)某个事件发生了。如果主题直接持有观察者的

shared_ptr
,那么即使观察者本身已经不再需要,主题也会阻止它被销毁。这同样会导致内存泄漏。使用
weak_ptr
,主题可以安全地持有观察者的引用。当观察者自行销毁后,主题在遍历观察者列表时,通过
weak_ptr::lock()
就能发现哪些观察者已经失效,从而将其从通知列表中移除。这让观察者能够独立于主题管理自己的生命周期。

Web Accessibility (WCAG 2 AA))
Web Accessibility (WCAG 2 AA))

将 WCAG 2 AA 无障碍标准应用于设计系统、UI 组件和网页——通过可运行的验证器进行对比度计算,采用设计 token 模式使颜色天生可访问,设定排版和焦点状态规则,并提供语义检查清单。适用于构建或审查 UI。

下载

再者,父子或层级结构。例如,在游戏引擎的场景图或UI控件树中,父节点通常会拥有子节点的

shared_ptr
,这很合理。但子节点可能也需要访问它的父节点(例如,为了获取全局坐标或查找兄弟节点)。如果子节点也持有父节点的
shared_ptr
,那就又形成了循环引用。这时,子节点持有父节点的
weak_ptr
就是完美的解决方案。子节点可以通过
weak_ptr
安全地访问父节点,而不会影响父节点的生命周期。如果父节点被销毁了,子节点也能感知到,并做出相应处理。

最后,资源管理器。有时我们会有一些昂贵的资源(如数据库连接、网络套接字),它们可能被多个模块共享。一个中央的资源管理器可能持有这些资源的

shared_ptr
。而各个模块在使用这些资源时,如果只是需要临时访问而不希望延长其生命周期,就可以从资源管理器那里获取一个
weak_ptr
。这样,当资源管理器决定释放某个资源时(例如,因为长时间未使用或系统负载过高),各个模块的
weak_ptr
就会感知到资源已经失效,从而避免了对已释放资源的访问。

这些场景都突出了一点:

weak_ptr
提供了一种“软引用”机制,它允许我们建立引用关系,但这种关系不会阻止对象的销毁。这在设计复杂、动态的系统时,提供了极大的灵活性和安全性。

weak_ptr
和
shared_ptr
之间如何安全转换与协作?

weak_ptr
和
shared_ptr
的协作是C++智能指针设计哲学中一个非常精妙的部分,它提供了一种既能共享所有权又能安全观察的机制。它们之间的转换与协作,主要围绕着
lock()
方法展开。

首先,

weak_ptr
的创建。一个
weak_ptr
通常是从一个
shared_ptr
或者另一个
weak_ptr
构造出来的。例如:

std::shared_ptr<MyObject> s_ptr = std::make_shared<MyObject>("Initial");
std::weak_ptr<MyObject> w_ptr1 = s_ptr; // 从shared_ptr创建
std::weak_ptr<MyObject> w_ptr2 = w_ptr1; // 从另一个weak_ptr创建

需要注意的是,

weak_ptr
不能直接从原始指针或
unique_ptr
创建。它必须依附于一个已经由
shared_ptr
管理的资源。

其次,从

weak_ptr
到
shared_ptr
的安全转换
。这是
weak_ptr
最核心的用法。由于
weak_ptr
不拥有对象,它指向的对象随时可能被销毁。因此,直接解引用
weak_ptr
是不安全的。为了安全地访问对象,我们必须将其“升级”为一个
shared_ptr
。这个过程是通过
lock()
方法完成的:

std::weak_ptr<MyObject> weak_obj_ptr = ...; // 假设已经有一个weak_ptr

if (auto shared_obj = weak_obj_ptr.lock()) {
    // 如果lock()成功,shared_obj现在是一个有效的shared_ptr
    // 并且在此作用域内,它会增加对象的引用计数,确保对象不会被销毁
    shared_obj->doSomething();
    std::cout << "Successfully locked and accessed object: " << shared_obj->name << std::endl;
} else {
    // lock()返回空,说明对象已经被销毁
    std::cout << "Failed to lock: object has been destroyed." << std::endl;
}

这里

lock()
方法的返回值是一个
shared_ptr
。如果对象仍然存活,它会返回一个指向该对象的
shared_ptr
,这会暂时增加对象的引用计数,保证在当前
shared_ptr
的作用域内对象不会被意外销毁。如果对象已经销毁,
lock()
会返回一个空的
shared_ptr
。这种模式是使用
weak_ptr
进行生命周期监控的标准且安全的方式。

再次,

expired()
方法。
weak_ptr
还有一个
expired()
方法,它返回一个布尔值,指示它所指向的对象是否已经被销毁。

if (weak_obj_ptr.expired()) {
    std::cout << "Object is already expired." << std::endl;
} else {
    std::cout << "Object is not expired yet." << std::endl;
}

然而,

expired()
方法本身并不提供线程安全性。也就是说,即使
expired()
返回
false
,在紧接着调用
lock()
之前,对象也可能在另一个线程中被销毁。因此,总是推荐使用
lock()
来尝试访问对象,而不是先用
expired()
判断再尝试访问。
lock()
是原子操作,它能确保在返回有效的
shared_ptr
时,对象是确实存在的。

最后,

weak_ptr
和
shared_ptr
的协作,本质上是所有权和观察者模式的结合。
shared_ptr
负责管理对象的生命周期和所有权,而
weak_ptr
则提供了一种轻量级的、非侵入式的观察机制,它允许你“窥视”一个对象是否仍然存在,而不会干扰它的销毁。这种分工使得在复杂系统中管理资源和对象关系变得更加清晰和安全。

热门AI工具

更多
函子Zion

一款支持AI搭建、可视化编辑和多端发布的全栈无代码开发平台。

花生AI
花生AI Hot

一款AI视频创作工具,主要用于B站推出的AI视频创作工具,适合需要提升相关任务效率的用户。

创客贴AI

创客贴AI是一款AI图像与设计工具,AI辅助的智能在线设计工具。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

小橡皮AI

一款AI工具,主要用于小橡皮一键去AI味,将机器味重的内容改得更有活人感,防止因为 AI 内容被平台打上 AI 标签,以及限流,适合需要提升相关任务效率的用户。

PixPix
PixPix Hot

PixPix是一款面向电商视觉生产的AI商品图生成工具。

Laper
Laper Hot

Laper是专为编剧、导演和制片人推出的 AI 原生剧本创作工具。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

相关专题

更多
C++ 智能指针与现代内存管理
C++ 智能指针与现代内存管理

深入讲解 C++ 现代内存管理的核心工具——智能指针,涵盖 unique_ptr 独占所有权语义、shared_ptr 引用计数机制与循环引用问题、weak_ptr 弱引用的应用场景、make_unique/make_shared 工厂函数的性能优势、自定义删除器的编写、RAII 资源管理思想的实践,以及从裸指针迁移到智能指针的重构策略,帮助开发者编写安全无泄漏的现代 C++ 代码。

379

2026.04.23

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

4778

2023.08.10

空指针异常处理
空指针异常处理

本专题整合了空指针异常解决方法,阅读专题下面的文章了解更多详细内容。

179

2025.11.16

数据库三范式
数据库三范式

数据库三范式是一种设计规范,用于规范化关系型数据库中的数据结构,它通过消除冗余数据、提高数据库性能和数据一致性,提供了一种有效的数据库设计方法。本专题提供数据库三范式相关的文章、下载和课程。

3125

2023.06.29

如何删除数据库
如何删除数据库

删除数据库是指在MySQL中完全移除一个数据库及其所包含的所有数据和结构,作用包括:1、释放存储空间;2、确保数据的安全性;3、提高数据库的整体性能,加速查询和操作的执行速度。尽管删除数据库具有一些好处,但在执行任何删除操作之前,务必谨慎操作,并备份重要的数据。删除数据库将永久性地删除所有相关数据和结构,无法回滚。

4181

2023.08.14

vb怎么连接数据库
vb怎么连接数据库

在VB中,连接数据库通常使用ADO(ActiveX 数据对象)或 DAO(Data Access Objects)这两个技术来实现:1、引入ADO库;2、创建ADO连接对象;3、配置连接字符串;4、打开连接;5、执行SQL语句;6、处理查询结果;7、关闭连接即可。

3511

2023.08.31

MySQL恢复数据库
MySQL恢复数据库

MySQL恢复数据库的方法有使用物理备份恢复、使用逻辑备份恢复、使用二进制日志恢复和使用数据库复制进行恢复等。本专题为大家提供MySQL数据库相关的文章、下载、课程内容,供大家免费下载体验。

1087

2023.09.05

vb中怎么连接access数据库
vb中怎么连接access数据库

vb中连接access数据库的步骤包括引用必要的命名空间、创建连接字符串、创建连接对象、打开连接、执行SQL语句和关闭连接。本专题为大家提供连接access数据库相关的文章、下载、课程内容,供大家免费下载体验。

3047

2023.10.09

Postman身份认证怎么配置
Postman身份认证怎么配置

本专题从实际API接口调试场景出发,讲解Postman身份认证的完整配置流程,包括常见认证协议介绍、授权参数设置、访问令牌填写、OAuth 2.0授权及认证失败排查,帮助用户正确完成接口授权与访问验证。

60

2026.11.06

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C++ Core Guidelines
C++ Core Guidelines

共0课时 | 0人学习

C++ Reference
C++ Reference

共0课时 | 0人学习

C++ 官方标准说明
C++ 官方标准说明

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn