(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

相关推荐
励志不掉头发的内向程序员1 小时前
STL库——string(类函数学习)
开发语言·c++
一百天成为python专家1 小时前
Python循环语句 从入门到精通
开发语言·人工智能·python·opencv·支持向量机·计算机视觉
Sunhen_Qiletian1 小时前
朝花夕拾(五)--------Python 中函数、库及接口的详解
开发语言·python
hqwest2 小时前
C#WPF实战出真汁07--【系统设置】--菜品类型设置
开发语言·c#·wpf·grid设计·stackpanel布局
前路不黑暗@2 小时前
C语言:操作符详解(二)
c语言·开发语言·经验分享·笔记·学习·学习方法·visual studio
深盾科技3 小时前
Kotlin Data Classes 快速上手
android·开发语言·kotlin
zzywxc7873 小时前
详细探讨AI在金融、医疗、教育和制造业四大领域的具体落地案例,并通过代码、流程图、Prompt示例和图表等方式展示这些应用的实际效果。
开发语言·javascript·人工智能·深度学习·金融·prompt·流程图
浮灯Foden3 小时前
算法-每日一题(DAY13)两数之和
开发语言·数据结构·c++·算法·leetcode·面试·散列表
淡海水3 小时前
【原理】Struct 和 Class 辨析
开发语言·c++·c#·struct·class
Q_Q19632884753 小时前
python的电影院座位管理可视化数据分析系统
开发语言·spring boot·python·django·flask·node.js·php