首页 > 后端开发 > C++ > 正文

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

P粉602998670
发布: 2025-09-14 12:52:01
原创
624人浏览过
shared_ptr循环引用导致内存泄漏,通过weak_ptr打破循环。示例中A强引用B,B弱引用A,避免了析构时引用计数无法归零的问题,确保对象正确销毁。

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

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

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

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

class A {
public:
    std::string name;
    std::shared_ptr<B> 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) {
        b_ptr = b;
    }
};

class B {
public:
    std::string name;
    std::weak_ptr<A> 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) {
        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<A> pa = std::make_shared<A>("Object_A");
        std::shared_ptr<B> pb = std::make_shared<B>("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>
登录后复制
成员,同时B对象也有一个
shared_ptr<A>
登录后复制
成员。当它们各自初始化并相互引用后,A的引用计数会因为B持有它而增加,B的引用计数也会因为A持有它而增加。

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

shared_ptr<A> pa
登录后复制
,此时A的引用计数是1。 接着你创建了一个
shared_ptr<B> 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<A>
登录后复制
而不是
shared_ptr<A>
登录后复制
。那么,整个引用链条就会变成这样:

AI建筑知识问答
AI建筑知识问答

用人工智能ChatGPT帮你解答所有建筑问题

AI建筑知识问答 22
查看详情 AI建筑知识问答
  1. pa
    登录后复制
    持有
    shared_ptr<A>
    登录后复制
    ,A的引用计数为1。
  2. pb
    登录后复制
    持有
    shared_ptr<B>
    登录后复制
    ,B的引用计数为1。
  3. pa
    登录后复制
    内部持有
    shared_ptr<B>
    登录后复制
    ,B的引用计数变为2。
  4. pb
    登录后复制
    内部持有
    weak_ptr<A>
    登录后复制
    。注意,
    weak_ptr
    登录后复制
    不会增加A的引用计数,所以A的引用计数仍然是1。

现在,当

pa
登录后复制
pb
登录后复制
超出作用域时:

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

等A被销毁后,

pb
登录后复制
内部的那个
weak_ptr<A>
登录后复制
就会自动失效(
expired()
登录后复制
方法会返回true)。 然后,当
pa
登录后复制
内部的
shared_ptr<B>
登录后复制
也超出作用域(或者说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
登录后复制
失效了,就知道这个对象已经被清理,可以重新创建或

以上就是C++内存管理基础中shared_ptr的循环引用问题解决的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

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