使用weak_ptr解决shared_ptr循环引用问题

news/2025/3/18 15:44:36/文章来源:https://www.cnblogs.com/Cycas/p/18779075

在C++中,使用 weak_ptr 可以解决 shared_ptr 的循环引用问题。循环引用通常发生在两个或多个对象通过 shared_ptr 互相引用,导致引用计数无法归零,内存无法释放。以下是详细解释和示例:


1. 循环引用的问题

假设两个类 AB 互相持有对方的 shared_ptr

#include <memory>
class B; // 前向声明class A {
public:std::shared_ptr<B> b_ptr;~A() { std::cout << "A destroyed\n"; }
};class B {
public:std::shared_ptr<A> a_ptr;~B() { std::cout << "B destroyed\n"; }
};int main() {std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->b_ptr = b;  // A 引用 Bb->a_ptr = a;  // B 引用 A// 退出作用域时,a 和 b 的引用计数均为 1,内存泄漏!return 0;
}

运行结果:无析构输出(对象未被销毁)。


2. 使用 weak_ptr 解决循环引用

将其中一个 shared_ptr 改为 weak_ptr,打破循环依赖:

#include <memory>
class B;class A {
public:std::shared_ptr<B> b_ptr;~A() { std::cout << "A destroyed\n"; }
};class B {
public:std::weak_ptr<A> a_weak;  // 使用 weak_ptr 代替 shared_ptr~B() { std::cout << "B destroyed\n"; }
};int main() {std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->b_ptr = b;b->a_weak = a;  // 弱引用 A// 退出作用域时:// a 的引用计数归零,A 被销毁;// 随后 b 的引用计数归零,B 被销毁。return 0;
}

运行结果:

A destroyed
B destroyed

3. weak_ptr 的关键机制

  • 不增加引用计数
    weak_ptr 不会增加对象的引用计数,因此不会阻止对象被销毁。
  • 通过 lock() 安全访问
    需要访问对象时,使用 lock() 方法获取一个临时的 shared_ptr,若对象已被销毁,返回空指针:
    void B::some_method() {if (auto a_ptr = a_weak.lock()) {  // 安全访问a_ptr->do_something();} else {std::cout << "A already destroyed\n";}
    }
    

4. 典型应用场景

  • 观察者模式
    观察者持有被观察对象的 weak_ptr,避免影响被观察对象的生命周期。
  • 缓存
    缓存中存储 weak_ptr,允许对象在无外部引用时自动释放。
  • 解决父子对象循环引用
    子对象通过 weak_ptr 引用父对象,父对象通过 shared_ptr 管理子对象。

5. 总结

工具 作用 引用计数 生命周期影响
shared_ptr 共享所有权 +1 延长生命周期
weak_ptr 观察但不拥有,解决循环引用 不增加 无影响

关键规则

  • 若对象之间存在双向依赖,将其中一个方向的引用改为 weak_ptr
  • 使用 lock() 安全访问 weak_ptr 指向的对象。

A和B类均使用弱引用

在C++中,当两个类(例如 AB互相持有对方的 weak_ptr 时,可以避免循环引用问题,但需要结合具体场景判断是否合理。以下是详细分析:


1. 代码示例及结果

#include <memory>
class B;class A {
public:std::weak_ptr<B> b_weak;  // A 弱引用 B~A() { std::cout << "A destroyed\n"; }
};class B {
public:std::weak_ptr<A> a_weak;  // B 弱引用 A~B() { std::cout << "B destroyed\n"; }
};int main() {std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->b_weak = b;  // A 的 weak_ptr 指向 Bb->a_weak = a;  // B 的 weak_ptr 指向 Areturn 0;
}

运行结果

A destroyed
B destroyed

对象 AB 会被正确销毁,无内存泄漏


2. 双方使用 weak_ptr 的可行性

(1) 可以解决循环引用

  • weak_ptr 不增加引用计数,因此 ABshared_ptr 引用计数在 main 函数结束时均会归零,对象被释放。
  • 不会形成循环依赖,因为 weak_ptr 不会阻止对象销毁。

(2) 访问对象时需谨慎

  • 通过 weak_ptr 访问对象时,必须调用 lock() 获取临时的 shared_ptr,并检查是否为空:
    void A::some_method() {if (auto b = b_weak.lock()) {  // 安全访问 Bb->do_something();} else {// B 已被销毁}
    }
    
  • 若双方仅通过 weak_ptr 引用,且没有其他 shared_ptr 持有对象,则 lock() 可能返回空指针。

3. 适用场景

双方使用 weak_ptr 的场景需满足以下条件:

  1. 对象的生命周期由外部控制
    例如,AB 的存活由第三方 shared_ptr 管理(如全局对象、容器持有等),二者仅需“观察”对方。
  2. 不依赖对方的长期存在
    允许在访问时对方可能已被销毁,代码需处理 lock() 返回空的情况。
  3. 逻辑上无需强依赖关系
    例如,AB 是观察者模式中的观察目标,彼此仅需弱引用。

4. 对比单方向 weak_ptr 的优劣

方案 优点 缺点
单方向 weak_ptr 明确主从关系,代码逻辑清晰 需设计方向(如父对象持有子对象的 shared_ptr
双方 weak_ptr 完全解耦,灵活性高 需频繁检查对象有效性,代码复杂度高

5. 总结

  • 可以双方使用 weak_ptr:只要对象的生命周期由外部 shared_ptr 管理,且代码能处理对象可能失效的情况。
  • 更常见的设计:单方向 weak_ptr(如父子关系中,子对象弱引用父对象),逻辑更清晰。
  • 注意事项
    • 始终用 lock() 检查 weak_ptr 的有效性。
    • 若对象需长期存在,应由某个 shared_ptr 持有(如全局对象或容器)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/900972.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

20222223 实验一《Python程序设计》实验报告

20222223 2024-2025-2 《Python程序设计》实验一报告课程:《Python程序设计》 班级: 2222 姓名: 李東霖 学号: 20222223 实验教师:王志强 实验日期:2025年3月12日 必修/选修: 公选课一、实验内容 1.熟悉Python开发环境; 2.练习Python运行、调试技能;(编写书中的程序…

交换节点 - 环

树上交换节点给定一棵树,每个节点有一个权值。现在每次可以交换任意两个节点的权值,请问最少多少次交换可以使得每个节点的权值等于它的编号? 保证给出的权值是一个排列,也就是说保证一定有解。时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 256M,其他语言512M输入描…

Arduino雷达使用Android移动应用程序

该系统可以检测障碍物并收集距离和角度信息,并且可以通过移动应用程序通过蓝牙进行控制。 Arduino雷达使用Android移动应用程序本项目是一个由超声波传感器和伺服电机驱动的雷达系统。该系统可以检测障碍物并收集距离和角度信息,并且可以通过移动应用程序通过蓝牙进行控制。该…

使用cursor打造智能客服demo

cursor AI它真的是非常强大。 今天讲下如何使用它,搭配deepseek api接口,来生成一个智能客服系统。这是最终的效果。首先cursor需要登录后才能使用。登录之后有两周的免费试用期。我们在窗口的右侧填写智能客服的需求。帮我实现一个网页智能客服。详细要求如下: 1.生成一个h…

【蓝牙小程序】小程序使用echart图表报错:setOption of undefined

转载自:https://developers.weixin.qq.com/community/develop/doc/0004ac054ccec0f26df7baa8756800问题:小程序使用echart图表报错 Cannot read property setOption of undefined;at api request success callback function TypeError: Cannot read property setOption of un…

前端中的Javascript

前端中的Javascript javascript定义方式内联JavaScript直接在HTML元素的事件属性中编写JavaScript代码<body><h1>Hello, World!</h1><button onclick="alert(Button clicked!)">Click Me</button> </body>内部JavaScript可以直接…

NVM:安装配置使用

一、简介 在实际的开发和学习中可能会遇到不同项目的 node 版本不同,而出现的兼容性问题。 而 nvm 就可以很好的解决这个问题,它可以在同一台机器上下管理多个 node 版本,使得程序员可以轻松地安装、卸载和切换不同的 node 版本。 在下载和配置 nvm 前,需要在控制面板中先删…

硬盘科普,M.2,PCI-E,NVMe 傻傻分不清

首先从三个层面去理解这个问题:物理接口,通道,协议 1:物理接口(相当于通讯中的电,光口) 大白话- 物理规格,像是 公路,铁路 专门跑PCI-E通道的那个物理接口:扩展性极强,可以插显卡的PCI-E X16的那个物理接口,或者插网卡,声卡的那个PCI-E X1那个物理接口,都是属于一类…

第二届长城杯ciscn半决赛awdp pwn以及应急响应wp

这次半决赛还真是状况频出,先是上午全场靶机断联了2轮,下午的应急又在坐大牢,还好是后面捋顺了逻辑做出来了,下半场干了个赛区第二,总成绩第四,这回是真燃尽了 上半场AWDP typo fix 一开始一直在改这道结果后面才发现那个prompt是真的好改,白浪费了3轮。。。 进入程序是…

LLM Assistance for Memory Safety

LLM Assistance for Memory SafetyMohammed, Nausheen, et al. "LLM Assistance for Memory Safety." 2025 IEEE/ACM 47th International Conference on Software Engineering (ICSE). IEEE Computer Society, 2024.Introduction 在软件安全的漏洞中,内存安全是主要…

『Plotly实战指南』--折线图绘制进阶篇

上一篇介绍了Plotly绘制折线图的基础知识和数据预处理的技巧, 本文将重点探讨如何利用Plotly实现多线折线图的布局设计以及动态折线图的实现, 让我们一起掌握进阶的折线图绘制技巧。 1. 多折线图布局 在实际的数据分析场景中,常常需要同时展示多组数据,例如对比不同产品的销…

使用gradio快速实现聊天机器人

我们可以使用gradio库通过低代码的方式快速实现聊天机器人界面及交互: import gradio as gr from ollama import chatdef predict(message,history):stream = chat(model="deepseek-r1:1.5b",messages=[{"role":"user","content":mess…