(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

相关推荐
杯莫停丶3 小时前
设计模式之:模板模式
开发语言·设计模式
开发者小天3 小时前
调整为 dart-sass 支持的语法,将深度选择器/deep/调整为::v-deep
开发语言·前端·javascript·vue.js·uni-app·sass·1024程序员节
老猿讲编程4 小时前
C++中的奇异递归模板模式CRTP
开发语言·c++
Yupureki5 小时前
从零开始的C++学习生活 16:C++11新特性全解析
c语言·数据结构·c++·学习·visual studio
紫荆鱼5 小时前
设计模式-迭代器模式(Iterator)
c++·后端·设计模式·迭代器模式
汤姆yu6 小时前
基于python的化妆品销售分析系统
开发语言·python·化妆品销售分析
ScilogyHunter6 小时前
C语言标准库完全指南
c语言·开发语言
sali-tec6 小时前
C# 基于halcon的视觉工作流-章52-生成标定板
开发语言·图像处理·人工智能·算法·计算机视觉
应茶茶6 小时前
C++11 核心新特性:从语法重构到工程化实践
java·开发语言·c++
程子的小段6 小时前
C 语言实例 - 字符串复制
c语言·开发语言