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),不会阻止资源析构。

相关推荐
mCell13 小时前
GSAP ScrollTrigger 详解
前端·javascript·动效
gnip13 小时前
Node.js 子进程:child_process
前端·javascript
excel16 小时前
为什么在 Three.js 中平面能产生“起伏效果”?
前端
excel17 小时前
Node.js 断言与测试框架示例对比
前端
天蓝色的鱼鱼19 小时前
前端开发者的组件设计之痛:为什么我的组件总是难以维护?
前端·react.js
codingandsleeping19 小时前
使用orval自动拉取swagger文档并生成ts接口
前端·javascript
石金龙20 小时前
[译] Composition in CSS
前端·css
白水清风20 小时前
微前端学习记录(qiankun、wujie、micro-app)
前端·javascript·前端工程化
Ticnix20 小时前
函数封装实现Echarts多表渲染/叠加渲染
前端·echarts
用户221520442780020 小时前
new、原型和原型链浅析
前端·javascript