C++ 的循环引用问题

循环引用是指多个对象通过std::shared_ptr相互引用,形成一个闭环,导致它们的引用计数无法降为0.即使这些对象不再被程序的其他部分引用,它们的内存也不会被释放,因为每个对象的std::shared_ptr都被其他对象持有。循环引用会导致内存泄漏,因为资源无法被析构。std::shared_ptr的引用计数机制依赖于计数归零来释放资源,循环引用破坏了这一机制。

以下示例展示了两个类AB相互持有对方的std::shared_ptr:

cpp 复制代码
#include <iostream>
#include <memory>

class B; // 前向声明

class A {
public:
    std::shared_ptr<B> b_ptr; // A 持有 B 的 shared_ptr
    ~A() { std::cout << "A destructed\n"; }
};

class B {
public:
    std::shared_ptr<A> a_ptr; // B 持有 A 的 shared_ptr
    ~B() { std::cout << "B destructed\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 持有 B
        b->a_ptr = a; // B 持有 A
        std::cout << "Reference count of a: " << a.use_count() << "\n"; // 2
        std::cout << "Reference count of b: " << b.use_count() << "\n"; // 2
    } // 离开作用域,a 和 b 的引用计数仍为 1,不会析构
    std::cout << "Scope exited\n";
    return 0;
}

输出:

cpp 复制代码
Reference count of a: 2
Reference count of b: 2
Scope exited

说明

  • amain 中的 std::shared_ptr<A>,指向 Object A(引用计数 = 1)。
  • bmain 中的 std::shared_ptr<B>,指向 Object B(引用计数 = 1)。
  • Object A 的成员 b_ptrstd::shared_ptr<B>)指向 Object B,使 Object B 的引用计数增至 2。
  • Object B 的成员 a_ptrstd::shared_ptr<A>)指向 Object A,使 Object A 的引用计数增至 2。
  • 形成循环:A -> B -> A

离开作用域时

  • a 销毁,Object A 的引用计数从 2 减到 1(因为 Ba_ptr 仍在引用)。
  • b 销毁,Object B 的引用计数从 2 减到 1(因为 Ab_ptr 仍在引用)。
  • 结果:Object AObject B 的引用计数都不为 0,无法释放,内存泄漏。

图示

通过将一方的 std::shared_ptr 替换为 std::weak_ptr,可以打破循环引用。std::weak_ptr 引用资源但不增加引用计数,因此不会阻止资源释放。

cpp 复制代码
#include <iostream>
#include <memory>

class B;
class A {
public:
    std::shared_ptr<B> b_ptr;
    ~A() { std::cout << "A destructed\n"; }
};

class B {
public:
    std::weak_ptr<A> a_ptr; // 使用 weak_ptr
    ~B() { std::cout << "B destructed\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 持有 B
    b->a_ptr = a; // B 弱引用 A
    return 0;
}

输出

css 复制代码
B destructed
A destructed

说明

  • a 指向 Object A(引用计数 = 1)。
  • b 指向 Object B(引用计数 = 1)。
  • Object Ab_ptrstd::shared_ptr<B>)指向 Object BObject B 的引用计数增至 2。
  • Object Ba_ptrstd::weak_ptr<A>)指向 Object A,但不增加 Object A 的引用计数(仍为 1)。
  • 没有形成强引用循环,只有单向强引用:A -> B

离开作用域时

  • a 销毁,Object A 的引用计数从 1 减到 0,Object A 被释放,触发 A 的析构函数。
  • Object A 销毁时,其 b_ptr 销毁,Object B 的引用计数从 2 减到 1。
  • b 销毁,Object B 的引用计数从 1 减到 0,Object B 被释放,触发 B 的析构函数。
  • 结果:资源正确释放,无内存泄漏。

图示

std::weak_ptr 引用资源但不控制其生命周期(不增加引用计数)。当资源被所有 std::shared_ptr 释放后,std::weak_ptr 自动变为"过期"(expired),不会阻止资源析构。

相关推荐
IT_陈寒3 小时前
SpringBoot这个自动配置坑我跳了三次
前端·人工智能·后端
kyriewen3 小时前
我用 AI 一周写完了整个项目,上线第一天就崩了——这是我踩过最贵的 5 个坑
前端·javascript·ai编程
牧艺4 小时前
从零到协同:构建类飞书在线文档系统的五个技术重难点
前端·人工智能
红尘散仙4 小时前
想写一个像样的终端 App?试试把 React 的开发体验搬进 Rust TUI
前端·rust
袋鼠云数栈UED团队5 小时前
一套 Spec-First 的 AI 编程工作流
前端·人工智能
袋鼠云数栈前端5 小时前
一套 Spec-First 的 AI 编程工作流
前端·ai+
angerdream5 小时前
Android手把手编写儿童手机远程监控App之vue3 路由守卫
前端
不服老的小黑哥5 小时前
AI规范驱动编程-harness工程项目实战
前端
vivo互联网技术5 小时前
从 Web 到桌面:基于 Tauri 2.0 + Vue 3 打造 vivo 线下门店「大头贴」拍照体验系统
前端·rust
光影少年5 小时前
React 合成事件机制、和原生事件区别、事件冒泡阻止
前端·react.js·掘金·金石计划