当前位置: 首页 > news >正文

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

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 对象引用计数为 1
        std::shared_ptr<B> b1(new B); //B 对象引用计数为 1
        a1->bptr = b1; //A 对象引用计数加 1
        b1->aptr = a1; //B 对象引用计数加 1
        
        //此时 A 和 B 对象的引用计数均为 2
    } 
    //离开作用域后
    //由于 a1 和 b1 的生命周期结束, 所以 A 对象引用计数减 1, B 对象引用计数也减 1
    //但 bptr 和 aptr , 引用计数为 1
    
    return 0;
}

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

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

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


如何解决循环引用

方式一

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

{
	   std::shared_ptr<A> a1(new A); //A 对象引用计数为 1
	   std::shared_ptr<B> b1(new B); //B 对象引用计数为 1
	   a1->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 对象引用计数为 1
        std::shared_ptr<B> b1(new B); //B 对象引用计数为 1
        a1->bptr = b1; //A 对象引用计数加 1
        b1->aptr = a1; //B 对象引用计数加 1
        
        //此时 A 和 B 对象的引用计数均为 2
        b1->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; //输出1
    weak_ptr<int> wp1 = sp;
    weak_ptr<int> wp2 = sp;
    cout<< sp.use_count() <<endl; //输出1
}

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


三 🏠 结语

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

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

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

相关文章:

  • python之selenium入门教程
  • Power BI散点图
  • 分享|2023年全球市场准入认证咨讯
  • synchronized底层原理
  • Java多线程之CAS中的ABA问题与JUC的常见类
  • 【Android -- 开源库】权限适配 RxPermissions 的基本使用
  • Unity-TCP-网络聊天功能(二): 登录与注册
  • 51单片机最强模块化封装(1)
  • Python学习-----起步2(变量与转义符)
  • Java程序设计实验3 | 面向对象(上)
  • 优秀的代码最终选择if else,还是switch case
  • Openharmony的编译构建--进阶篇1
  • 每天一道大厂SQL题【Day02】电商场景TopK统计
  • EMT4J详细介绍与使用,帮你找到Java版本升级带来的问题,让你在项目jdk升级不在头疼
  • 第2章:使用CSS定义样式
  • 【数据结构】动图详解单向链表
  • MySQL基础篇笔记
  • Vue3现状—必然趋势?
  • uniapp获取支付宝user_id - 支付宝提现 - 登录授权 - APP支付宝登陆 - H5支付宝授权
  • Promise详解与手写实现
  • 瑞士成第15届北影节主宾国,6部佳作闪耀“瑞士电影周”
  • 广东音像城清退,发烧友紧急“淘宝”,曾见证广州音乐黄金期
  • “女子被前男友泼汽油烧伤案”二审将于22日开庭,一审判12年
  • 韩国一战机飞行训练中掉落机炮吊舱和空油箱
  • 王毅、董军将主持召开中印尼外长防长“2+2”对话机制首次部长级会议
  • 俄外交部谴责日本计划在近俄边境军事演习