0

0

C++内存管理基础中shared_ptr的循环引用问题解决

P粉602998670

P粉602998670

发布时间:2025-09-14 12:52:01

|

634人浏览过

|

来源于php中文网

原创

shared_ptr循环引用导致内存泄漏,通过weak_ptr打破循环。示例中A强引用B,B弱引用A,避免了析构时引用计数无法归零的问题,确保对象正确销毁。

c++内存管理基础中shared_ptr的循环引用问题解决

shared_ptr
循环引用是C++内存管理中一个常见的陷阱,它会导致对象无法被正确销毁,进而引发内存泄漏。解决这个问题的核心思路是打破对象间强引用的循环,通常通过引入
weak_ptr
来将循环中的一个强引用替换为弱引用。
weak_ptr
不增加对象的引用计数,允许对象在没有其他强引用时被正常销毁,从而避免泄漏。

#include 
#include 
#include 

// 前向声明,因为A和B互相引用
class B;

class A {
public:
    std::string name;
    std::shared_ptr b_ptr; // A强引用B

    A(std::string n) : name(n) {
        std::cout << "A " << name << " constructor\n";
    }
    ~A() {
        std::cout << "A " << name << " destructor\n";
    }

    void set_b(std::shared_ptr b) {
        b_ptr = b;
    }
};

class B {
public:
    std::string name;
    std::weak_ptr a_ptr; // B弱引用A,这是打破循环的关键

    B(std::string n) : name(n) {
        std::cout << "B " << name << " constructor\n";
    }
    ~B() {
        std::cout << "B " << name << " destructor\n";
    }

    void set_a(std::shared_ptr a) {
        a_ptr = a;
    }

    void check_a_status() {
        if (auto shared_a = a_ptr.lock()) { // 尝试提升为shared_ptr
            std::cout << "B " << name << " observes A " << shared_a->name << " is still alive.\n";
        } else {
            std::cout << "B " << name << " observes A is gone.\n";
        }
    }
};

int main() {
    std::cout << "--- 示例开始 ---\n";
    {
        std::shared_ptr pa = std::make_shared("Object_A");
        std::shared_ptr pb = std::make_shared("Object_B");

        std::cout << "初始引用计数:pa=" << pa.use_count() << ", pb=" << pb.use_count() << "\n";

        // 建立引用关系
        pa->set_b(pb); // A强引用B,B的引用计数增加
        pb->set_a(pa); // B弱引用A,A的引用计数不变

        std::cout << "建立引用后:pa=" << pa.use_count() << ", pb=" << pb.use_count() << "\n";
        // 此时,pa的use_count应为1 (main函数持有),pb的use_count应为2 (main函数持有,A对象内部持有)

        pb->check_a_status(); // B尝试访问A,此时A应该还活着
    } // pa和pb超出作用域,智能指针自动析构
    std::cout << "--- 示例结束 ---\n";
    // 如果没有循环引用,A和B的析构函数都会被调用
    return 0;
}

为什么我的
shared_ptr
对象迟迟不销毁?深入理解循环引用是如何形成的

我们经常会遇到这样的情况:你用

shared_ptr
管理资源,觉得万无一失了,结果程序跑着跑着,内存占用就上去了,那些本该被释放的对象却还在内存里躺着。这多半就是循环引用在作祟。

它的形成其实很简单,就是两个或多个对象,它们之间相互持有对方的

shared_ptr
。比如,A对象有一个
shared_ptr
成员,同时B对象也有一个
shared_ptr
成员。当它们各自初始化并相互引用后,A的引用计数会因为B持有它而增加,B的引用计数也会因为A持有它而增加。

想象一下这个过程: 你创建了一个

shared_ptr pa
,此时A的引用计数是1。 接着你创建了一个
shared_ptr pb
,B的引用计数也是1。 然后,你让
pa
内部的成员指向
pb
。这时,B的引用计数从1变成了2(
pb
持有一次,
pa
内部持有一次)。 再接着,你让
pb
内部的成员指向
pa
。这时,A的引用计数从1变成了2(
pa
持有一次,
pb
内部持有一次)。

现在问题来了,当

pa
pb
这两个局部变量超出
作用域时,它们会尝试销毁自己持有的
shared_ptr
pa
销毁,A的引用计数从2变成1。
pb
销毁,B的引用计数从2变成1。但注意,A的内部仍然持有一个
shared_ptr
指向B,B的内部也仍然持有一个
shared_ptr
指向A。这意味着A和B的引用计数永远不会降到0。它们会一直“互相指着对方”,谁也无法先走一步,最终导致内存泄漏。

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

这就像两个人手拉着手站在悬崖边,谁也松不开,因为一旦松开,对方就可能掉下去。但实际上,只要有一个人愿意放手,或者至少不是用“死死抓住”的方式握着,这个僵局就能打破。这种“死死抓住”就是

shared_ptr
的强引用特性,它确保只要有一个
shared_ptr
存在,对象就不会被销毁。

深入浅出
weak_ptr
:它是如何巧妙地打破循环引用的?

weak_ptr
,正如其名,是一个“弱”指针。它不拥有对象的所有权,也不会增加对象的引用计数。这正是它能打破循环引用的关键所在。你可以把它理解成一个“观察者”或者“旁观者”,它只是知道某个对象可能存在,但它不参与对象的生命周期管理。

当我们用

weak_ptr
替换循环引用中的一个
shared_ptr
时,比如在上面的A和B的例子中,让B持有
weak_ptr
而不是
shared_ptr
。那么,整个引用链条就会变成这样:

元典智库
元典智库

元典智库:智能开放的法律搜索引擎

下载
  1. pa
    持有
    shared_ptr
    ,A的引用计数为1。
  2. pb
    持有
    shared_ptr
    ,B的引用计数为1。
  3. pa
    内部持有
    shared_ptr
    ,B的引用计数变为2。
  4. pb
    内部持有
    weak_ptr
    。注意,
    weak_ptr
    不会增加A的引用计数,所以A的引用计数仍然是1。

现在,当

pa
pb
超出作用域时:

  1. pa
    销毁,A的引用计数从1降到0。此时,A对象被销毁。
  2. pb
    销毁,B的引用计数从2降到1(只剩下
    pa
    内部持有的那个)。

等A被销毁后,

pb
内部的那个
weak_ptr
就会自动失效(
expired()
方法会返回true)。 然后,当
pa
内部的
shared_ptr
也超出作用域(或者说A对象被销毁时,其成员
b_ptr
也会被销毁),B的引用计数从1降到0。B对象也被销毁。

看,这样一来,A和B都能被正常销毁了。

weak_ptr
就像是循环中的一个“软连接”,它允许你访问对象,但不会阻碍对象的销毁。当你需要使用
weak_ptr
指向的对象时,你必须先调用它的
lock()
方法,尝试将其提升为一个
shared_ptr
。如果对象仍然存在,
lock()
会返回一个有效的
shared_ptr
;如果对象已经被销毁了,它会返回一个空的
shared_ptr
。这提供了一种安全地访问可能已被销毁对象的方式,避免了悬空指针的问题。

这种机制在父子关系、观察者模式或者缓存管理中非常有用。比如一个父节点拥有子节点,子节点需要知道它的父节点是谁,但子节点不应该拥有父节点,否则就会形成循环。这时,子节点持有父节点的

weak_ptr
就是非常自然且正确的选择。

除了打破循环引用,
weak_ptr
还有哪些实用的应用场景?

weak_ptr
的作用远不止打破循环引用这么简单,它在很多场景下都能发挥独特的优势,主要体现在需要“观察”但不“拥有”对象所有权的需求上。

一个很典型的场景就是观察者模式(Observer Pattern)。在观察者模式中,一个主题(Subject)对象会维护一个观察者(Observer)列表,并在状态改变时通知所有观察者。如果主题持有观察者的

shared_ptr
,而观察者又反过来持有主题的
shared_ptr
(比如为了获取主题的信息),那么就会形成循环引用。正确的做法是,主题持有观察者的
weak_ptr
。这样,当一个观察者不再被其他地方强引用时,它就可以被销毁,而主题并不会阻止它的销毁。主题在通知观察者时,只需要尝试
lock()
这个
weak_ptr
,如果成功,就说明观察者还活着,可以通知;如果失败,说明观察者已经“去世”了,可以将其从列表中移除。

另一个常见应用是缓存(Cache)机制。假设你有一个对象缓存,里面存放着一些昂贵的对象。你希望这些对象在没有其他地方使用它们时能够被自动清理,以节省内存。如果缓存直接持有这些对象的

shared_ptr
,那么只要对象在缓存里,它的引用计数就不会降到0,永远不会被清理。这时,缓存就应该持有这些对象的
weak_ptr
。当外部代码需要某个对象时,它可以通过缓存获取一个
shared_ptr
。当所有外部
shared_ptr
都失效后,对象就会被销毁,缓存中的
weak_ptr
也会随之失效。下次请求时,缓存发现
weak_ptr
失效了,就知道这个对象已经被清理,可以重新创建或

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
空指针异常处理
空指针异常处理

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

22

2025.11.16

2026赚钱平台入口大全
2026赚钱平台入口大全

2026年最新赚钱平台入口汇总,涵盖任务众包、内容创作、电商运营、技能变现等多类正规渠道,助你轻松开启副业增收之路。阅读专题下面的文章了解更多详细内容。

33

2026.01.31

高干文在线阅读网站大全
高干文在线阅读网站大全

汇集热门1v1高干文免费阅读资源,涵盖都市言情、京味大院、军旅高干等经典题材,情节紧凑、人物鲜明。阅读专题下面的文章了解更多详细内容。

32

2026.01.31

无需付费的漫画app大全
无需付费的漫画app大全

想找真正免费又无套路的漫画App?本合集精选多款永久免费、资源丰富、无广告干扰的优质漫画应用,涵盖国漫、日漫、韩漫及经典老番,满足各类阅读需求。阅读专题下面的文章了解更多详细内容。

33

2026.01.31

漫画免费在线观看地址大全
漫画免费在线观看地址大全

想找免费又资源丰富的漫画网站?本合集精选2025-2026年热门平台,涵盖国漫、日漫、韩漫等多类型作品,支持高清流畅阅读与离线缓存。阅读专题下面的文章了解更多详细内容。

7

2026.01.31

漫画防走失登陆入口大全
漫画防走失登陆入口大全

2026最新漫画防走失登录入口合集,汇总多个稳定可用网址,助你畅享高清无广告漫画阅读体验。阅读专题下面的文章了解更多详细内容。

11

2026.01.31

php多线程怎么实现
php多线程怎么实现

PHP本身不支持原生多线程,但可通过扩展如pthreads、Swoole或结合多进程、协程等方式实现并发处理。阅读专题下面的文章了解更多详细内容。

1

2026.01.31

php如何运行环境
php如何运行环境

本合集详细介绍PHP运行环境的搭建与配置方法,涵盖Windows、Linux及Mac系统下的安装步骤、常见问题及解决方案。阅读专题下面的文章了解更多详细内容。

0

2026.01.31

php环境变量如何设置
php环境变量如何设置

本合集详细讲解PHP环境变量的设置方法,涵盖Windows、Linux及常见服务器环境配置技巧,助你快速掌握环境变量的正确配置。阅读专题下面的文章了解更多详细内容。

0

2026.01.31

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C# 教程
C# 教程

共94课时 | 8.1万人学习

C 教程
C 教程

共75课时 | 4.3万人学习

C++教程
C++教程

共115课时 | 15万人学习

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号