【C++】:智能指针 -- RAII思想shared_ptr剖析

目录

  • 一,内存泄漏
  • 二,智能指针的使用及原理
    • 2.1 RAII思想
    • 2.2 auto_ptr
    • 2.3 unique_ptr
  • 三,shared_ptr(重点)
    • 3.1 shared_ptr的原理及使用
    • 3.2 shared_ptr的模拟实现
      • 1. 基本框架
      • 2. 引用计数的设计
      • 3. 拷贝构造
      • 4. 析构函数
      • 5. 赋值拷贝
    • 3.3 shared_ptr的循环引用
    • 3.4 定制删除器
    • 3.5 shared_ptr实现的完整代码

点击跳转上一篇文章: 【C++】:错误处理机制 – 异常

一,内存泄漏

(1) 什么是内存泄漏

内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费

(2) 内存泄漏的危害

长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死

void MemoryLeaks()
{// 1.内存申请了忘记释放int* p1 = (int*)malloc(sizeof(int));int* p2 = new int;// 2.异常安全问题int* p3 = new int[10];// 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.Func(); delete[] p3;
}

二,智能指针的使用及原理

2.1 RAII思想

RAII 是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。

在对象构造时获取资源接着控制对资源的访问使之在对象的生命周期内始终保持有效最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象

这种做法有两大好处

(1) 不需要显式地释放资源
(2) 采用这种方式,对象所需的资源在其生命期内始终保持有效

// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
public:SmartPtr(T* ptr = nullptr): _ptr(ptr){}~SmartPtr(){if(_ptr)delete _ptr;} T& operator*() {return *_ptr;}T* operator->() {return _ptr;}
private:T* _ptr;
};int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}void Func()
{Shard_Ptr<int> sp1(new int);Shard_Ptr<int> sp2(new int);cout << div() << endl;
}int main()
{try {Func();}catch(const exception& e){cout<<e.what()<<endl;}return 0;
}

总结一下智能指针的原理

(1) RAII特性
(2) 重载operator*和opertaor->,具有像指针一样的行为

2.2 auto_ptr

C++98版本的库中就提供了auto_ptr的智能指针auto_ptr 支持拷贝,但是拷贝时,管理权限转移,会造成被拷贝指针悬空

使用方法如下

struct Date
{int _year;int _month;int _day;Date(int year = 1, int month = 1, int day = 1):_year(year), _month(month), _day(day){}~Date(){cout << "~Date()" << endl;}
};int main()
{auto_ptr<Date> ap1(new Date);//拷贝时,管理权限转移,被拷贝(ap1)指针悬空auto_ptr<Date> ap2(ap1);// 此时ap1为空指针了,访问直接报错!//ap1->_year++;return 0;
}

结论:auto_ptr是一个失败设计,很多公司明确要求不能使用auto_ptr

2.3 unique_ptr

C++11中开始提供更靠谱的unique_ptrunique_ptr的实现原理简单粗暴的防拷贝

使用方法如下

struct Date
{int _year;int _month;int _day;Date(int year = 1, int month = 1, int day = 1):_year(year), _month(month), _day(day){}~Date(){cout << "~Date()" << endl;}
};int main()
{unique_ptr<Date> up1(new Date);//不支持拷贝//unique_ptr<Date> up2(up1);return 0;
}

三,shared_ptr(重点)

C++11中开始提供更靠谱的并且支持拷贝的shared_ptr

3.1 shared_ptr的原理及使用

shared_ptr的原理是通过引用计数的方式来实现多个shared_ptr对象之间共享资源

原理实现的具体细节

(1) shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享
(2) 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
(3) 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源
(4) 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

使用方法如下

struct Date
{int _year;int _month;int _day;Date(int year = 1, int month = 1, int day = 1):_year(year), _month(month), _day(day){}~Date(){cout << "~Date()" << endl;}
};int main()
{shared_ptr<Date> sp1(new Date);shared_ptr<Date> sp2(sp1);shared_ptr<Date> sp3(sp2);return 0;
}

3.2 shared_ptr的模拟实现

1. 基本框架

namespace bit
{template<class T>class shared_ptr{public:// 构造,拷贝等其他接口.....T* get()const{return _ptr;}int use_count()const{return *_pcount;}// 重载运算符,模拟指针的行为T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr ;int* _pcount; //引用计数function<void(T* ptr)> _del = [](T* ptr) {delete ptr; };// function的默认构造中没有可调用对象,不给缺省值会报错
}

2. 引用计数的设计

每个资源都要配一个引用计数,是用来记录有多少个对象共同指向这块资源的。

不是每个对象都配一个计数,也不能直接使用static静态变量,这样所有对象都用一个计数了,显然不合理

所以我们要在堆上开一块空间保存计数,用一个指针指向这个计数,当每次有对象指向同一块空间时,就可以找到这个指针指向的计数++,析构时计数- -每次构造的时候就出现新资源,所以要在构造的时候申请

shared_ptr(T* ptr = nullptr):_ptr(ptr),_pcount(new int(1)) //每个资源给一个计数
{}

在这里插入图片描述

3. 拷贝构造

把一个对象拷贝给另一个对象,说明这个对象的资源与另一个对象共享了,计数++

// sp2(sp1)
shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount)
{(*_pcount)++;
}

4. 析构函数

如果引用计数到0,说明已经没有对象指向这块资源了,就要释放该资源

void release()
{if (--(*_pcount) == 0){//delete _ptr;_del(_ptr);delete _pcount;_ptr = nullptr;_pcount = nullptr;}
}~shared_ptr()
{release();
}

5. 赋值拷贝

赋值拷贝是已经存在的两个对象之间。所以赋值时要注意那个对象原先资源的处理,原先的计数要先- -。并且要注意避免自己给自己赋值

// sp1 = sp3
shared_ptr<T>& operator=(shared_ptr<T>& sp)
{// 避免自己给自己赋值。用资源的指针判断// 指向同一块资源就不白费赋值if (_ptr != sp._ptr){release();_ptr = sp._ptr;_pcount = sp._pcount;(*_pcount)++;}return *this;
}

在这里插入图片描述

3.3 shared_ptr的循环引用

循环引用问题时一个巨坑,出现时必然会导致内存泄漏问题。

struct ListNode
{int _data;shared_ptr<ListNode> _next;shared_ptr<ListNode> _prev;~ListNode(){cout << "~ListNode()" << endl;}
};int main()
{//循环引用--内存泄漏shared_ptr<ListNode> n1(new ListNode);shared_ptr<ListNode> n2(new ListNode);cout << n1.use_count() << endl;cout << n2.use_count() << endl;n1->_next = n2;n2->_prev = n1;cout << n1.use_count() << endl;cout << n2.use_count() << endl;return 0;
}

循环引用分析图解

在这里插入图片描述

解决方案:在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了
原理就是:weak_ptr不支持管理资源,不支持RAII。n1->_next = node2和n2->_prev = n1时,weak_ptr的_next和_prev不会增加n1和n2的引用计数

使用weak_ptr要包含头文件

#include <functional>
struct ListNode
{int _data;weak_ptr<ListNode> _next;weak_ptr<ListNode> _prev;~ListNode(){cout << "~ListNode()" << endl;}
};

在我们自己的shared_ptr中也进行简单的模拟实现

template<class T>
class weak_ptr
{
public:weak_ptr(){}// 不增加引用计数weak_ptr(const shared_ptr<T>& sp):_ptr(sp.get()){}weak_ptr<T>& operator=(const shared_ptr<T>& sp){_ptr = sp.get();return *this;}

3.4 定制删除器

如果不是new出来的对象如何通过智能指针管理呢其实shared_ptr设计了一个删除器来解决这个问题。(ps:删除器这个问题我们了解一下)

仿函数的删除器

template <class T>
class DeleteArray
{
public:void operator()(T* ptr){delete[] ptr;}
};class Fclose
{
public:void operator()(FILE* ptr){cout << "fclose:" << ptr << endl;fclose(ptr);}
};int main()
{shared_ptr<Date[]> sp4(new Date[5]);shared_ptr<FILE> sp5(fopen("test.cpp", "r"), Fclose());return 0;
}

但是每次写仿函数还是有些麻烦,所以可以在shared_ptr的类中进行实现

//定制删除器
template<class D>
shared_ptr(T* ptr, D del):_ptr(ptr), _pcount(new int(1)),_del(del)
{}

3.5 shared_ptr实现的完整代码

shared_ptr.h

#pragma once
#include <functional>namespace bit
{template<class T>class shared_ptr{public:// RAIIshared_ptr(T* ptr = nullptr):_ptr(ptr),_pcount(new int(1)) //每个资源给一个计数{}//定制删除器template<class D>shared_ptr(T* ptr, D del):_ptr(ptr), _pcount(new int(1)),_del(del){}// sp2(sp1)shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount){(*_pcount)++;}// sp1 = sp3shared_ptr<T>& operator=(shared_ptr<T>& sp){// 避免自己给自己赋值。用资源的指针判断// 指向同一块资源就不白费赋值if (_ptr != sp._ptr){release();_ptr = sp._ptr;_pcount = sp._pcount;(*_pcount)++;}return *this;}void release(){if (--(*_pcount) == 0){//delete _ptr;_del(_ptr);delete _pcount;_ptr = nullptr;_pcount = nullptr;}}~shared_ptr(){release();}T* get()const{return _ptr;}int use_count()const{return *_pcount;}// 重载运算符,模拟指针的行为T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr ;int* _pcount; //引用计数function<void(T* ptr)> _del = [](T* ptr) {delete ptr; };// function的默认构造中没有可调用对象,不给缺省值会报错};template<class T>class weak_ptr{public:weak_ptr(){}weak_ptr(const shared_ptr<T>& sp):_ptr(sp.get()){}weak_ptr<T>& operator=(const shared_ptr<T>& sp){_ptr = sp.get();return *this;}private:T* _ptr = nullptr;};
}

Test.cpp

int main()
{bit::shared_ptr<Date> sp1(new Date);bit::shared_ptr<Date> sp2(sp1);bit::shared_ptr<Date> sp3(new Date);sp1 = sp3;//传了删除器,就用自己传的,没传就用缺省的bit::shared_ptr<FILE> sp5(fopen("test.cpp", "r"), Fclose());bit::shared_ptr<int> sp6((int*)malloc(40), [](int* ptr){cout << "free:" << ptr << endl;free(ptr);});return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/394485.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

详解Xilinx FPGA高速串行收发器GTX/GTP(4)--TX/RX接口的数据位宽和时钟设计

目录 1、时钟设计 2、TX接口 3、接口位宽与时钟的关系 4、时钟来源方案 5、TX端内部的时钟分频设计 6、RX接口 文章总目录点这里:《FPGA接口与协议》专栏的说明与导航 1、时钟设计 GT收发器内部比较复杂,所使用的时钟就不止一个,比较主要的时钟有两个,架构…

Zookeeper的监听机制及原理解析

系列文章目录 手把手教你安装Zookeeper 及可视化插件ZooInspector、ZKUI Zookeeper入门篇&#xff0c;了解ZK存储特点 使用Zookeeper的监听及原理解析 系列文章目录前言一、监听机制的基本概念二、Zookeeper监听原理1. 事件类型2. 监听模式与监听器类型&#xff08;1&#xff…

健康管理系统

目录 第1章 系统概述 第2章 可行性研究 2.1 项目背景及意义 2.2 可行性研究 第3章 需求分析 3.1 功能性需求 3.2 非功能性需求 3.2.1 性能需求 第4章 总体设计 4.1 技术架构 4.2功能模块设计 第5章 详细设计 5.1 主页 5.2 写剧本杀 5.3 剧本杀分类管理 5.4 个人…

数组下标越界异常(ArrayIndexOutOfBoundsException)以及解决方案

在Java学习的初期&#xff0c;我们往往可能会遇到一些程序的错误提示&#xff0c;告诉我们&#xff0c;程序出现了某些不正常的情况&#xff0c;在这种情况发生时&#xff0c;我们一般称之为出现了异常。 我们目前有两类常见的错误&#xff1a; 一个是编译时异常 &#xff0c…

数据分析与应用:微信-情人节红包流向探索分析

目录 0 需求描述 1 红包发送方用户的基本信息缺失率有多高?(即有多少红包发送方用户无法在用户基本信息表中匹配? 2 哪一组红包金额的拒收率最高? 3、最受二线城市欢迎的红包金额为?(即发出次数最多) 4 北上广深 4 大城市中,哪座城市的男性用户发出的 520 红包比例…

三大口诀不一样的代码,小小的制表符和换行符玩的溜呀

# 小案例&#xff0c;打印输出加法口诀 for i in range(1,10):for j in range(1,10):if j>i:breakprint(f"{j}{i}{ji}".strip(),end\t)print() print(\n) for i in range(1,10):for j in range(1,10):if j>i:breakprint(f"{j}x{i}{j*i}",end\t)print…

计算机毕业设计选题推荐-房屋租赁系统-Java/Python项目实战

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

app逆向抓包技巧:SSL Pinning检测绕过

本篇博客旨在记录学习过程&#xff0c;不可用于商用等其它途径 场景 在charles抓包下&#xff0c;某斑马app在注册时发现点击登录毫无反应&#xff0c;看抓包结果提示SSL handshake with client failed&#xff0c;确定是触发了SSL/TLS Pinning&#xff08;证书锁定&#xff…

【SpringBoot 属性加载机制】

SpringBoot 属性加载 一个 SpringBoot 应用的配置属性可以有多种不同的来源, 比如可以来自操作系统的环境变量, 比如可以来自 application.yaml 文件; 每一种不同的属性来源, 都会被 SpringBoot 封装成一个PropertySource对象, 保存在 Environment 对象的 PropertySources 类型…

数据采集工具之Canal

本文主要介绍canal采集mysql数据的tcp、datahub(kafka)模式如何实现 1、下载canal https://aliyun-datahub.oss-cn-hangzhou.aliyuncs.com/tools/canal.deployer-1.1.5-SNAPSHOT.tar.gz canal的原理类似于mysql的主从复制&#xff0c;canal模拟的是从节点拉取主节点的binlog数…

LeetCode 热题 HOT 100 (015/100)【宇宙最简单版】

【栈】No. 0155 最小栈【中等】&#x1f449;力扣对应题目指路 希望对你有帮助呀&#xff01;&#xff01;&#x1f49c;&#x1f49c; 如有更好理解的思路&#xff0c;欢迎大家留言补充 ~ 一起加油叭 &#x1f4a6; 欢迎关注、订阅专栏 【力扣详解】谢谢你的支持&#xff01; …

深入了解核函数:连接机器学习与统计学的桥梁

引言 在机器学习中&#xff0c;支持向量机&#xff08;SVM&#xff09;是一种强大的监督学习模型&#xff0c;特别适合处理分类问题。然而&#xff0c;SVM最初被设计用于线性可分的数据集&#xff0c;现实中的数据往往不是线性可分的。为了解决这一问题&#xff0c;我们引入了…

共享之道——享元模式(Python实现)

共享之道——享元模式&#xff08;Python实现&#xff09; 大家好&#xff0c;今天我们继续来讲结构型设计模式&#xff0c;上一期我们介绍了外观模式&#xff0c;这一期我们来讲享元模式&#xff08;Flyweight Pattern&#xff09;。 享元模式&#xff08;Flyweight Pattern…

Bitwise 首席投资官:忽略短期的市场波动,关注加密货币的发展前景

原文标题&#xff1a;《The Crypto Market Sell-Off: What Happened and Where We Go From Here》撰文&#xff1a;Matt Hougan&#xff0c;Bitwise 首席投资官编译&#xff1a;Chris&#xff0c;Techub News 加密货币市场在周末经历了大幅下跌。从上周五下午 4 点到周一早上 7…

优质电器/机械岗位推荐:经验不限大厂直招,薪资最高30K!

本周优质电器/机械岗位推荐&#xff0c;涵盖C、自动化、开发、安卓开发、项目管理等岗位&#xff0c;经验不限&#xff0c;更有大厂直招岗位&#xff0c;薪资最高30K&#xff01;&#xff01; 抓紧投递&#xff0c;早投早入职&#xff01; &#x1f447;点击职位名称查看详情…

PHP + Laravel + RabbitMQ + Redis 实现消息队列 (三) 消费队列在RabbitMQ和redis中的发布和订阅

发布订阅&#xff08;Pub/Sub&#xff09; 对于消息队列传统的模式来说&#xff0c;一个消费者消费一条消息&#xff0c;这条消息被消费之后就不会再次被其它的消费者消费。但是在发布订阅模式中&#xff0c;一条消息是可以被多个消费者消费的&#xff0c;这些消费者其实相当于…

前端构建工具|vite快速入门

认识vite vite组成部分 Vite是一种新型前端构建工具&#xff0c;能够显著提升前端开发体验。它主要由两部分组成&#xff1a; 一个开发服务器&#xff0c;它基于 原生 ES 模块 提供了 丰富的内建功能&#xff0c;如速度快到惊人的 模块热更新&#xff08;HMR&#xff09;。一…

C++——类模板经典案例——自定义通用数组类

案例&#xff1a;自定义数组类 需求&#xff1a; 1&#xff0c;对内置数据及自定义数据类型的数据存储 2&#xff0c;将数组中的数据存储到堆区 3&#xff0c;构造函数中可以存入数组的容量 4&#xff0c;提供对应的拷贝构造函数和运算符重载防止浅拷贝问题的发生 5&#xff0c…

基于Springboot + Vue的宿舍管理系统

前言 文末获取源码数据库 感兴趣的可以先收藏起来&#xff0c;需要学编程的可以给我留言咨询&#xff0c;希望帮助更多的人 精彩专栏推荐订阅 不然下次找不到哟 Java精品毕设原创实战项目 作者的B站地址&#xff1a;程序员云翼的个人空间-程序员云翼个人主页-哔哩哔哩视频 csd…

vue3+axios请求导出excel文件

在Vue 3中使用axios请求导出Excel文件&#xff0c;可以发送一个GET或POST请求&#xff0c;并设置响应类型为blob或arraybuffer&#xff0c;然后使用new Blob()构造函数创建一个二进制文件&#xff0c;最后使用URL.createObjectURL()生成一个可以下载的链接。 先看代码 import…