C++ 浅谈之智能指针 shared_ptr 循环引用

news/2024/11/25 4:40:13/

C++ 浅谈之智能指针 shared_ptr 循环引用

HELLO,各位博友好,我是阿呆 🙈🙈🙈

这里是 C++ 浅谈系列,收录在专栏 C++ 语言中 😜😜😜

本系列阿呆将记录一些 C++ 语言重要的语法特性 🏃🏃🏃

OK,兄弟们,废话不多直接开冲 🌞🌞🌞


一 🏠 概述

智能指针是什么:

智能指针是⼀个 RAII 类模型,⽤于动态分配内存,其设计思想是将基本类型指针封装为(模板)类对象指针,并在离开作⽤域时调⽤析构函数,使⽤ delete 删除指针所指向的内存空间 🐌🐌🐌

注:RAII 机制是一种对资源申请、释放操作的封装


shared_ptr 智能指针:

对于 shared_ptr ,实现共享式拥有的概念,即多个智能指针可以指向相同的对象,该对象及相关资源会在其所指对象不再使⽤之后,自动释放与对象相关的资源 🐳🐳🐳


shared_ptr 实现原理:

  1. 构造函数中计数初始化为 1
  2. 拷⻉构造函数中计数值加 1
  3. 赋值运算符中,左边的对象引⽤计数减 1,右边的对象引⽤计数加 1
  4. 析构函数中引⽤计数减 1
  5. 在赋值运算符和析构函数中,如果减 1 后为 0 ,则调⽤ delete 释放对象 ✌ ✌ ✌

二 🏠 核心

什么是循环引用

两个类对象相互使用 shared_ptr 指向对方,从而造成内存泄漏 👊👊👊


先补充一个小知识 C++ 类成员的构造和析构顺序

创建对象时 🎅🎅🎅

① 若有父类,执行父类构造函数

② 类非静态数据成员,按声明顺序创建

③ 执行该类构造函数

销毁对象时 👳👳👳

① 调用该类析构函数

② 销毁数据成员,与创建时顺序相反

③ 若有父类,调用父类析构函数


再看如下所示循环引用代码 👇👇👇

a1 引用计数减 1,b1 引用计数也减 1#include <iostream>
#include <memory>
using namespace std;class A;
class B;class A {
public:std::shared_ptr<B> bptr;~A() { cout << "A is deleted" << endl; } //析构后, 才释放成员 bptr
};class B {
public:std::shared_ptr<A> aptr;~B() { cout << "A is deleted" << endl; } //析构后, 才释放成员 aptr
};int main() {{std::shared_ptr<A> a1(new A); //A 对象引用计数为 1std::shared_ptr<B> b1(new B); //B 对象引用计数为 1a1->bptr = b1; //A 对象引用计数加 1b1->aptr = a1; //B 对象引用计数加 1//此时 A 和 B 对象的引用计数均为 2} //离开作用域后//由于 a1 和 b1 的生命周期结束, 所以 A 对象引用计数减 1, B 对象引用计数也减 1//但 bptr 和 aptr , 引用计数为 1return 0;
}

当 A 析构函数被调用时,才会释放成员变量 bptr,即当且仅当 A 对象的引用计数为 0

当 B 析构函数被调用时,才会释放成员变量 aptr,即当且仅当 B 对象的引用计数为 0

综上,两个类对象相互使用 shared_ptr 指向对方造成了类死锁现象,导致内存泄漏 🌻🌻🌻


如何解决循环引用

方式一

A 对象和 B 对象都成功析构,不会造成内存泄漏 👦👦👦

{std::shared_ptr<A> a1(new A); //A 对象引用计数为 1std::shared_ptr<B> b1(new B); //B 对象引用计数为 1a1->bptr = b1; //A 对象引用计数加 1//b1->aptr = a1;//此时 A 对象的引用计数为 2 //B 对象的引用计数为 1
}
//离开作用域后, 由于 a1 和 b1 的生命周期结束, 所以 A 对象引用计数减 1, B 对象引用计数也减 1
//此时 A 对象的引用计数为 1
//B 对象的引用计数为 0 (B 析构函数被调用), 释放成员变量 aptr
//此时 A 对象的引用计数为 0 (A 析构函数被调用)

方式二

A 对象和 B 对象都成功析构,不会造成内存泄漏 👨‍🚀👨‍🚀👨‍🚀

{std::shared_ptr<A> a1(new A); //A 对象引用计数为 1std::shared_ptr<B> b1(new B); //B 对象引用计数为 1a1->bptr = b1; //A 对象引用计数加 1b1->aptr = a1; //B 对象引用计数加 1//此时 A 和 B 对象的引用计数均为 2b1->aptr.reset(); //手动释放成员变量//此时 A 对象的引用计数为 2 //B 对象的引用计数为 1
}
//离开作用域后, 由于 a1 和 b1 的生命周期结束, 所以 A 对象引用计数减 1, B 对象引用计数也减 1
//此时 A 对象的引用计数为 1
//B 对象的引用计数为 0 (B 析构函数被调用), 释放成员变量 aptr
//此时 A 对象的引用计数为 0 (A 析构函数被调用)

方式三

那么有没有更优秀的解法呢 ?当然有,那就是 weak_ptr 🐌🐌🐌

weak_ptr:弱引用,不能控制对象的生命周期,且不是独立的智能指针,依赖于 shared_ptr 存在

int main()
{shared_ptr<int> sp(new int(10));cout<< sp.use_count() <<endl; //输出1weak_ptr<int> wp1 = sp;weak_ptr<int> wp2 = sp;cout<< sp.use_count() <<endl; //输出1
}

将上例任一类成员从 shared_ptr 改为 weak_ptr,即可解决循环引用问题 🐳🐳🐳


三 🏠 结语

身处于这个浮躁的社会,却有耐心看到这里,你一定是个很厉害的人吧 👍👍👍

各位博友觉得文章有帮助的话,别忘了点赞 + 关注哦,你们的鼓励就是我最大的动力

博主还会不断更新更优质的内容,加油吧!技术人! 💪💪💪


http://www.ppmy.cn/news/21561.html

相关文章

优化算法:曲径步长通优处,优化半天白优化

本文来自公众号“AI大道理” 训练一个神经网络&#xff0c;我们想要得到误差最小&#xff0c;就是要我们的损失函数最小。 如何得到最小值呢&#xff1f; 这就是优化算法。 梯度下降法是众多优化中的一种。 1、损失函数 2、GD&#xff08;梯度下降法&#xff09; 3、BGD&a…

python输出不重复的字符

项目场景&#xff1a; 输入一个字符串&#xff0c;把最左边的10个不重复的字符&#xff08;大小写算不同字符&#xff09;挑选出来。 如不重复的字符不到10个&#xff0c;则按实际数目输出。问题描述 输出一个字符串&#xff0c;包含字符串s最左边10个不重复的字符。不到10个…

iptables端口复用后门、sslh

iptables端口复用 创建端口复用链 创建端口复用规则将流量转到22端口 开启开关&#xff0c;接收到一个长为1139的icmp包&#xff0c;则将来源ip添加到LETMEIN表中 如果syn包来源ip处于letmein列表中&#xff0c;则跳转到LETMEIN链处理&#xff0c;有效时间为3600秒 开启复…

python之selenium入门教程

selenium&#xff0c;一个第三方库&#xff0c;可以通过给driver发送命令来操作浏览器&#xff0c;以达到模拟人操作浏览器&#xff0c;实现网页自动化、测试等&#xff0c;减少了重复性工作。 selenium的工作的基本架构如下&#xff1a; 安装 本文是在python环境下使用sele…

【小练】day2

day2 一、选择题 1. 使用printf函数打印一个double类型的数据&#xff0c;要求:输出为10进制&#xff0c;输出左对齐30个字符&#xff0c;4位精度。以下哪个选项是正确的? A %-30.4e B %4.30e C %-30.4f D %-4.30f #printf的格式控制 正确答案&#xff1a;C 2. 请找出下…

MySQL表中的聚合查询

聚合查询在MySQL初阶中进行的查询都是对于同一条记录的列与列之间进行的运算,那如何对多条记录的不同行进行运算呢(比如计算所有同学某一单科的总分,某一单科的平均分)?此时就需要聚合查询来操作了!1.聚合函数函数 说明COUNT([DISTINCT] expr)返回查询到的数据的数量SUM([DIST…

Docker常见面试题 | 答案

目录 1、Docker 是什么&#xff1f; 2、Docker的三大核心是什么? 3、仓库、镜像、容器的关系是&#xff1f; 4、Docker与虚拟机的区别 5、Docker容器的集中状态 6、如何把主机的东西拷贝到容器内部&#xff1f; 7、进入容器的方法有哪些&#xff1f; 8、如何让容器随着…

DDR调试不通?先别扔,这个操作可能帮你逆袭!

作者&#xff1a;一博科技高速先生成员 黄刚相信大家过完一个美美的春节后&#xff0c;学习的热情一定会暴涨&#xff0c;反正高速先生给大家分享技术文章的热情是非常高涨的哈&#xff01;打从推出这个系列的仿真和理论相结合的话题后&#xff0c;文章受到了很多忠实粉丝的喜爱…