【C/C++】强弱指针如何打破循环引用避免内存泄漏 ?

智能指针

创建一个智能指针:

std::shared_ptr<A> a = std::make_shared<A>(); // A任意对象

make_shared<>()是一种 安全、高效的智能对象构造方式,会在堆上同时分配一个对象和一个引用计数控制器(control block),并返回一个封装了它们的 shared_ptr,(控制块和对象是分别存储的,但 make_shared 会合并在一块内存中以优化分配)。你不用直接操作裸指针,它帮你全自动管理生命周期 。

什么是控制块(control block)?

控制块包含:

字段作用
strong_count有多少个 shared_ptr 指向这个对象
weak_count有多少个 weak_ptr 正在观察它
可能还有自定义析构器、删除器、调试信息等

控制块也分配在堆上,生命周期由引用计数控制。

智能对象结构图:

在这里插入图片描述

循环引用是怎么发生的 ?

struct B;  // 提前声明

struct A {
    std::shared_ptr<B> ptr_to_b;
    ~A() { std::cout << "A destroyed\n"; }
};

struct B {
    std::shared_ptr<A> ptr_to_a;
    ~B() { std::cout << "B destroyed\n"; }
};

void demo() {
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();

    a->ptr_to_b = b;
    b->ptr_to_a = a;
} // a 和 b 永远不会被销毁(内存泄漏)

逐步拆分代码
  1. 对象声明

struct B;  // 提前声明

struct A {
    std::shared_ptr<B> ptr_to_b; // 指向结构体B的智能指针
    ~A() { std::cout << "A destroyed\n"; }
}; 

struct B {
    std::shared_ptr<A> ptr_to_a; // 指向结构体A的智能指针
    ~B() { std::cout << "B destroyed\n"; }
};



在这里插入图片描述

  1. 创建智能对象
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();

创建智能对象会在堆上同时分配一个对象和一个引用计数控制器(control block),返回对应的智能指针。

在这里插入图片描述

  1. 循环引用
    a->ptr_to_b = b;
    b->ptr_to_a = a;

如图所示,两个智能对象相互引用,导致strong_count始终为1,相互锁死,都无法释放,造成内存泄漏。

注:图中将 shared_ptr 简化为直接指向目标对象,实际在底层它持有的是一份指向共享控制块(control block)的引用,并通过该控制块间接管理对象指针和引用计数。本图为了突出“循环引用”的结构逻辑,采用了直连对象的方式表示。

在这里插入图片描述

什么是strong_ptr、weak_ptr ?

shared_ptr:强引用(Strong Pointer)

是资源的“拥有者”,只要有一个 shared_ptr 存在,对象就不会被释放。

  • 拥有对资源的独立控制权

  • 每创建一个 shared_ptr,控制块中的 strong_count +1

  • 只有当所有 shared_ptr 被销毁时,资源才会被真正释放

  • 最常用的智能指针类型,负责延长资源生命周期

std::shared_ptr<MyObj> sp = std::make_shared<MyObj>();

weak_ptr:弱引用(Weak Pointer)

是资源的“观察者”,不拥有资源,也不影响其生命周期。

  • 不增加 strong_count,只影响控制块的 weak_count

  • 不能直接访问对象,必须先 .lock() 转成 shared_ptr

  • 常用于打破循环引用,防止内存泄漏

  • 适合缓存、观察、非拥有关系的场景

std::weak_ptr<MyObj> wp = sp;           // 观察 sp,但不控制它 
auto maybe = wp.lock();                 // 转为 shared_ptr(如果还活着) 
if (maybe) { maybe->doSomething(); }    // 安全使用

利用weak_ptr避免循环引用

struct B;

struct A {
    std::shared_ptr<B> ptr_to_b;
    ~A() { std::cout << "A destroyed\n"; }
};

struct B {
    std::weak_ptr<A> ptr_to_a;  // 用 weak_ptr 防止循环引用
    ~B() { std::cout << "B destroyed\n"; }
};

void demo() {
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();

    a->ptr_to_b = b;
    b->ptr_to_a = a;
} // A 和 B 都会被正常销毁

逐步拆分代码
struct B;

struct A {
    std::shared_ptr<B> ptr_to_b;
    ~A() { std::cout << "A destroyed\n"; }
};

---

auto a = std::make_shared<A>();

和前面相同,构造了一个指向B的智能对象:
在这里插入图片描述

但是,在B的结构中作出调制,改为weak_ptr。

struct B {
    std::weak_ptr<A> ptr_to_a;  // 用 weak_ptr 防止循环引用
    ~B() { std::cout << "B destroyed\n"; }
};

---

auto b = std::make_shared<B>();

在这里插入图片描述

    a->ptr_to_b = b;
    b->ptr_to_a = a;

std::weak_ptr 是一种不拥有资源的智能指针。它不直接指向目标对象本身,而是通过共享的控制块(control block)来观察该对象的状态,从而判断对象是否仍然存在。

请添加图片描述

将weak_ptr转化为shared_ptr会发生什么?

auto new_shared_ptr = b->ptr_to_a.lock();

weak_ptr.lock() 会通过共享的控制块(control block)创建一个新的 shared_ptr 实例,该实例与其他 shared_ptr 共同管理同一个对象,引用计数(strong_count)增加 1。

但不同于前面的循环引用,由于 weak_ptr 自身不持有资源,这不会引入循环引用。可以通过释放new_shared_ptr进而销毁(如果没有其他的shared_ptr)对象A,从而避免内存泄漏。
请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值