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

相关推荐
Samuel-Gyx40 分钟前
前端 CSS 样式书写与选择器 基础知识
前端·css
天天打码1 小时前
Rspack:字节跳动自研 Web 构建工具-基于 Rust打造高性能前端工具链
开发语言·前端·javascript·rust·开源
字节高级特工1 小时前
【C++】”如虎添翼“:模板初阶
java·c语言·前端·javascript·c++·学习·算法
db_lnn_20212 小时前
【vue】全局组件及组件模块抽离
前端·javascript·vue.js
Qin_jiangshan2 小时前
vue实现进度条带指针
前端·javascript·vue.js
菜鸟una2 小时前
【layout组件 与 路由镶嵌】vue3 后台管理系统
前端·vue.js·elementui·typescript
小张快跑。2 小时前
【Vue3】使用vite创建Vue3工程、Vue3基本语法讲解
前端·前端框架·vue3·vite
Zhen (Evan) Wang2 小时前
.NET 8 API 实现websocket,并在前端angular实现调用
前端·websocket·.net
星空寻流年3 小时前
css3响应式布局
前端·css·css3
Rverdoser3 小时前
代理服务器运行速度慢是什么原因
开发语言·前端·php