(C++) share_ptr 之循环引用

文章目录

🚩前言

自C++11起,有三大智能指针:

  • unique_ptr
  • shared_ptr
  • weak_ptr

都是内存管理中的非常重要的一部分动态内存管理 - cppreference.com

其中shared_ptr在实际应用中具有非常广泛的应用。

而由于其较unique_ptr的功能多,有引用计数的概念。导致存在一个名为循环引用的问题。这是一个非常经典的坑。但解决方案也不是很复杂。下面来一起看看吧。

🚩循环引用

🕹️例子1

Code

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

struct A;
struct B;

struct A {
    std::shared_ptr<B> ptr;
    A() {
        std::cout << __func__ << std::endl;
    }
    ~A() {
        std::cout << __func__ << std::endl;
    }
};

struct B {
    std::shared_ptr<A> ptr;
    B() {
        std::cout << __func__ << std::endl;
    }
    ~B() {
        std::cout << __func__ << std::endl;
    }
};

int main() {
    auto pa = std::make_shared<A>();
    auto pb = std::make_shared<B>();

    pa->ptr = pb;
    pb->ptr = pa;
}

😭shared_ptr (错误)

此代码,分别有两个类,class Aclass B。其中都有一个成员都是零一个类型的shared_ptr。

分别构造好对象后都去执行另一个对象,这就产生了著名的循环引用问题。

下面是输出结果,未能执行析构函数,导致内存泄漏

shell 复制代码
# 输出结果
A
B

逐步分析

当两个对象构造完毕后,两块share_ptr的智能指针的引用计数都为1,且内部的智能指针对象是默认空构造。

接着让两个内部的shared_ptr都指向对方,此时引用计数都为2。

此后,脱离作用域,开始RAII回收资源

此时两个在main函数中的shared_ptr对象都回收,两个引用计数都-1,变为1。

然后,就没有然后了。因此引用计数不为0,导致实际的对象内存不符合智能指针的要求,无法释放。

😂weak_ptr (正确)

假如此时将class B的智能指针改为weak_ptr。即可解决该问题。

weak_ptr其不会增加智能指针的引用计数,当实际需要使用的时候再构造出shared_ptr。这就是一种视图的作用。

逐步分析

当两个对象构造完毕后,两块share_ptr的智能指针的引用计数都为1,且内部的智能指针对象是默认空构造。

然后,B的weak指向A,A的引用计数块不变;A的share指向B,B的引用计数+1。Bcnt=2,Acnt=1

此后,脱离作用域,开始RAII回收资源

此时两个在main函数中的shared_ptr对象都回收,Bcnt=1Acnt=0

此时A符合回收条件,A资源释放,在A中的share也释放,B的计数-1=0。Bcnt=0

然后B也符合释放条件,B释放。

最终资源全部正确回收。

😭unique_ptr (错误)

我们再做一个假设,如果把class B的智能指针改为unique_ptr,会发生什么。

先公布答案:

shell 复制代码
A
B
~A
~B
~A

当然主函数也要略做修改,因为shared_ptr不能直接转化为unique_ptr。

cpp 复制代码
int main() {
    auto pa = std::make_shared<A>();
    auto pb = std::make_shared<B>();

    pa->ptr = pb;
    pb->ptr.reset(pa.get());
}

当然看到这里,敏感的朋友会直接发现这根本就是一种错误的编码。因为pa的对象直接让两个可以掌管生命周期的对象把控了。

所以,这就出现了A对象的两次析构。

这里的原理分析与上面类似,这里就不做过多展开了。

🕹️例子2

Code

除了定义两个不同的类,有可能会产生循环引用,一个类自身也是有可能会产生的。

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

struct Node {
    std::shared_ptr<Node> ptr;
    Node() {
        std::cout << __func__ << std::endl;
    }
    ~Node() {
        std::cout << __func__ << std::endl;
    }
};

int main() {
    auto p = std::make_shared<Node>();
    p->ptr = p;
}

这里解决方案一样,将内部的shared_ptr改为weak_ptr即可。

而要是这里改为unique_ptr时,有趣的事情就发生了,这里直接无限递归调用析构函数。

因为,当shared_ptr对象回收时,引用计数归0,导致Node对象回收。

当析构函数结束后,类内的unique_ptr也是回收资源,它指向的也是当前对象,++因此再次delete,也就是执行析构函数,析构成员++。

自此不断递归。。。

🚩END

关注我,学习更多C/C++,算法,计算机知识

B站:

👨‍💻主页:天赐细莲 bilibili

相关推荐
serve the people42 分钟前
解决osx-arm64平台上conda默认源没有提供 python=3.7 的官方编译版本的问题
开发语言·python·conda
柒七爱吃麻辣烫1 小时前
在Linux中安装JDK并且搭建Java环境
java·linux·开发语言
fallzzzzz1 小时前
C++ stl中的list的相关函数用法
c++·list
极小狐1 小时前
如何构建容器镜像并将其推送到极狐GitLab容器镜像库?
开发语言·数据库·机器学习·gitlab·ruby
多多*2 小时前
Java反射 八股版
java·开发语言·hive·python·sql·log4j·mybatis
正在走向自律2 小时前
从0到1:Python机器学习实战全攻略(8/10)
开发语言·python·机器学习
悦悦子a啊2 小时前
PTA:jmu-ds-最短路径
c++·算法·图论
FY_20182 小时前
键盘输出希腊字符方法
开发语言
西西弗Sisyphus2 小时前
Python 处理图像并生成 JSONL 元数据文件 - 灵活text版本
开发语言·python
小王努力学编程3 小时前
高并发内存池(三):TLS无锁访问以及Central Cache结构设计
jvm·数据结构·c++·学习