【C++杂货铺】智能指针

        

目录

🌈 前言🌈   

📁 内存泄漏

 📂 概念

 📂 分类 

 📂 如何避免

📁 RAII

📁 C++11智能指针

 📂 auto_ptr

 📂 unique_ptr 

 📂 shared_ptr

 📂 weaked_ptr

 📂 定制删除器

📁 C+11智能指针和boost智能指针关系

📁 总结


🌈 前言🌈   

        欢迎收看本期【C++杂货铺】,本期内容,主要讲解内存泄露的概念,如何避免内存泄漏,RAII是如何解决,引入智能指针对象,智能指针的三种类型,以及各自的优缺点,及其使用陷阱,此外,还将展示其底层实现。

📁 内存泄漏

 📂 概念

        内存泄漏是因为疏忽或错误造成程序未能释放已经不能使用的内存的情况,例如ner一段空间没有free。内存泄漏并不是指内存在物理上的小事,而是应用程序分配某段内存后,因为设计问题,失去了对该内存的控制,因而造成的内存的浪费。

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

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

 📂 分类 

● 堆内存泄漏(Heap leak)

        程序执行过程中,使用malloc/calloc/realloc/new等从堆中分配一块内存,用完后必须通过调用相应的free或者delete。假设程序的设计错误导致这部分内存没有释放,那么以后这段内存无法再被释放,就会产生Heap Leak。

● 系统资源泄露

        程序使用系统分配的资源,比如套接字,文件描述符,管道等没有使用对应的函数释放,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

在linux下内存泄漏检测:Linux下几款C++程序中的内存泄露检查工具_c++内存泄露工具分析-CSDN博客

在windows下使用第三方工具:VS编程内存泄漏:VLD(Visual LeakDetector)内存泄露库_visual leak detector vs2020-CSDN博客

其他工具:

https://www.cnblogs.com/liangxiaofeng/p/4318499.html

 📂 如何避免

        1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps: 这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智 能指针来管理才有保证。

        2. 采用RAII思想或者智能指针来管理资源。

        3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。

        4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。

        总结分为:1.事前预防型,如智能指针;2. 事后查错型,如内存泄漏工具。

📁 RAII

        RAII(Resource Acquisition Is Initialization)是一种利用对象声明后期来控制程序资源(如内存,文件句柄,网络连接,互斥量)的简单技术。

        在对象构造时获取资源,最后在对象析构时释放资源。借此,我们实际上把一份资源的责任托管给一个对象,这么做有两个好处:

        1. 不需要显示地释放资源。

        2. 对象所需要的资源在其生命周期内始终有效。

        总体来说,就是实现一个模板类,当模板类实例化出一个对象时,更构造函数提供一段堆内存空间,就获取一份资源,通过对象来管理,当出作用域,对象析构时,自动释放资源,因为析构是自动的。

📁 C++11智能指针

 📂 auto_ptr

        C++98版本中的库就提供了auto_ptr的智能指针。

        auto_ptr的实现原理:管理权转移的思想。

template<class T>
class auto_ptr
{
public:auto_ptr(T* ptr):_ptr(ptr){}auto_ptr(auto_ptr<T>& sp):_ptr(sp._ptr){// 管理权转移sp._ptr = nullptr;}auto_ptr<T>& operator=(auto_ptr<T>& ap){// 检测是否为自己给自己赋值if (this != &ap){// 释放当前对象中资源if (_ptr)delete _ptr;// 转移ap中资源到当前对象中_ptr = ap._ptr;ap._ptr = NULL;}return *this;}~auto_ptr(){if (_ptr){cout << "delete:" << _ptr << endl;delete _ptr;}}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};

        auto_ptr涉及管理权转移的问题,即一个指针指向一段空间,想要另一个指针也指向同一段空间,auto_ptr就交换两个智能指针指向。

        因此,实际中很少会使用auto_ptr,会导致需要问题,因此很多地方也禁止使用auto_ptr。

 📂 unique_ptr 

        如果我们想要两个指针指向同一段内存空间是可以的,但是智能指针是两个对象,当两个对象处在同一个作用域,也要指向同一段内存空间时,是可以的。当两个对象出作用域时,第一个完成析构,释放资源,第二个智能指针析构时,没有资源却依然释放,就会导致程序错误。

        如何解决这个问题?就引入了另外两个智能指针unique_str和shared_ptr。unique_ptr表示,既然你不能这样,那我直接不让你拷贝构造,赋值不就行了,每一个智能指针都只能指向不同的内存空间,不就解决问题了吗。

        原理非常简单,就是防拷贝。

template<class T>
class unique_ptr
{
public:unique_ptr(T* ptr):_ptr(ptr){}~unique_ptr(){if (_ptr){cout << "delete:" << _ptr << endl;delete _ptr;}}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}unique_ptr(const unique_ptr<T>& sp) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
private:T* _ptr;
};

        但是某些情况下,我们就是想要两个智能指针指向同一个内存空间,那么就要使用shared_ptr了。

 📂 shared_ptr

        C++11提供了更靠谱且能支持拷贝构造的shared_ptr。原理是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。

        shared_ptr在其内部,给每个资源都维护了一份计数,用来记录该资源被几个对象共享。        

        在对象析构时,先将计数-1,在判断是否为0,如果为0,表示没有指针指向,释放资源。如果不是0,就说明除了自己还有其他对象在使用改份资源,不能释放该资源,否则其他对象就变成野指针了。

template<class T>
class shared_ptr
{
public:shared_ptr(T* ptr = nullptr):_ptr(ptr), _pRefCount(new int(1)), _pmtx(new mutex){}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pRefCount(sp._pRefCount), _pmtx(sp._pmtx){AddRef();}void Release(){_pmtx->lock();bool flag = false;if (--(*_pRefCount) == 0 && _ptr){cout << "delete:" << _ptr << endl;delete _ptr;delete _pRefCount;flag = true;}_pmtx->unlock();if (flag == true){delete _pmtx;}}void AddRef(){_pmtx->lock();++(*_pRefCount);_pmtx->unlock();}shared_ptr<T>& operator=(const shared_ptr<T>& sp){//if (this != &sp)if (_ptr != sp._ptr){Release(); _ptr = sp._ptr;_pRefCount = sp._pRefCount;_pmtx = sp._pmtx;AddRef();}return *this;}int use_count(){return *_pRefCount;}~shared_ptr(){Release();}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}
private:T* _ptr;int* _pRefCount;mutex* _pmtx;
};

        上述模拟实现中,计数器使用指针,这样多个指向同一份资源的对象拥有同一份计数器。但是这里也引入了线程安全的问题,当多线程开发环境下,指针同时++,--需要保证原子性操作,所以这里int*,可以改为atomic<int>*,也可以使用锁来实现。

        智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时 ++或--,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2.这样引用计数就错 乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++、--是需要加锁的,也就是说引用计数的操作是线程安全的。

        但需要注意的是,std::shared_ptr保证了++,--等使用智能指针操作的线程安全,无法保证使用指向资源是线程安全的。

 📂 weaked_ptr

        shared_ptr的引用计数原理,十分的完美,但是它也有一个致命的问题,就是循环引用。当我们使用shared_ptr来使用双向链表,就会发现,最后无法析构了。

        Node1析构时,引用计数--,不为0,资源不会释放;Node2析构时,引用计数--,不为0,资源不会释放。这就会导致循环引用,双方都不释放资源,进而造成内存泄漏。

class Node
{
public:Node(){}~Node(){cout << "~Node()" << endl;}int _data = 0;weak_ptr<Node> _next;weak_ptr<Node> _prev;//shared_ptr<Node> _next;//shared_ptr<Node> _prev;
};int main()
{shared_ptr<Node>p1(new Node);shared_ptr<Node>p2(new Node);p1->_next = p2;p2->_prev = p1;return 0;
}

        weak_ptr引入,就是为了解决这个问题。weak_ptr并不直接参与 RAII(资源获取即初始化)原则,因为它本身不控制资源的生命周期。weak_ptr通常用于解决 C++ 中的循环引用问题,它指向一个由 shared_ptr管理的对象,但不增加引用计数,因此不会阻止对象的销毁。

        weak_ptr本身不拥有对象,就是一个弱引用,它并不符合RAII(对象生命周期控制资源),所以它不直接参与RAII,一般用来和shared_ptr配合,解决循环引用问题。

 📂 定制删除器

        智能指针构造是通过用户给出的内存空间来构造,析构默认使用delete来释放资源。但是如果我们使用智能指针来指向malloc出的空间,或者文件描述符等系统资源,就不能使用delete来释放资源。

        这是就需要我们来手写一个删除器,来告诉编译器怎么删除。本质就是一个底层执行一个回调函数,来释放资源。

        因此,底层就要一个可执行对象function来接收用户给定的定制删除器,如果用户没有指定,就使用默认delete删除。                                                                            

~shared_ptr()
{if (--(*_size) == 0){_del(_ptr);delete _size;}
}T* _ptr;
function<void(T*)> _del = [](T* ptr) {delete ptr;};

        上述是我们自己写个一个shared_ptr中一部分,下面展示如何使用:

class A
{
public:A(){}A(int a):_a(a){}~A(){}private:int _a = 10;;
};template<class T>
struct Freefunc
{void operator()(T* ptr){cout << "free ptr: " << ptr << endl;free(ptr);}
};int main()
{bit::shared_ptr<A> sp1(new A);bit::shared_ptr<int> sp2((int*)malloc(4), Freefunc<int>());bit::shared_ptr<FILE>sp3(fopen("test.txt", "w"), [](FILE* ptr) {fclose(ptr);});return 0;
}

        因此,定制删除器作用就是,将不能使用delete释放的资源,通过定制删除器,释放了。

📁 C+11智能指针和boost智能指针关系

        1. C++ 98 中产生了第一个智能指针auto_ptr.

         2. C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr.

         3. C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。

        4. C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost 的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。

        

📁 总结

        本期【C++杂货铺】主要内容,就是讲解什么是智能指针,RAII思想如何解决内存泄漏,三种智能指针类型,以及使用陷阱,需要注意什么,展示了模拟了底层实现。

        如果感觉本期内容对你有帮助,欢迎点赞,收藏,关注Thanks♪(・ω・)ノ

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

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

相关文章

电子电器架构 --- 智能汽车的大脑(域控制器)

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…

QT--进程

一、进程QProcess QProcess 用于启动和控制外部进程&#xff0c;管理其输入输出流。 使用方法 start()&#xff1a;启动一个新进程。setStandardInputFile()&#xff1a;将文件作为标准输入。将进程的标准输入&#xff08;stdin&#xff09;重定向到指定的文件。换句话说&am…

AV1技术学习:Constrained Directional Enhancement Filter

CDEF允许编解码器沿某些(可能是倾斜的)方向应用非线性消阶滤波器。它以88为单位进行。如下图所示&#xff0c;通过旋转和反射所示的三个模板来定义八个预设方向。 Templates of preset directions and their associated directions. The templates correspond to directions of…

MATLAB: ode45 求解常微分方程

引入 ode45 是 MATLAB 中用于求解非刚性常微分方程&#xff08;ODE&#xff09;的数值方法。它基于 Runge-Kutta 方法&#xff0c;并具有自适应步长调整机制&#xff0c;能够在一定误差控制范围内高效地计算 ODE 的数值解。 下面我们通过这个包含详细注释的代码&#xff0c;一…

Windows 11 系统对磁盘进行分区保姆级教程

Windows 11磁盘分区 磁盘分区是将硬盘驱动器划分为多个逻辑部分的过程&#xff0c;每个逻辑部分都可以独立使用和管理。在Windows 11操作系统中进行磁盘分区主要有以下几个作用和意义&#xff1a; 组织和管理数据&#xff1a;分区可以帮助用户更好地组织他们的数据&#xff0c…

无人机之降落操作及紧急情况处理

一、无人机降落操作 1、选择降落地点 a.提前选择一个平坦且没有障碍物的降落点&#xff1b; b.确认降落点周围没有行人或障碍物&#xff0c;保证降落的安全性。 2、降低飞行高度 a.缓慢降低飞行高度&#xff0c;尽量保持匀速下降&#xff0c;防止因下降过快导致无人机受损…

Day20 | 39. 组合总和 40.组合总和II 131.分割回文串

语言 Java 39. 组合总和 组合总和 题目 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidate…

最新可用度盘不限速后台系统源码_去授权开心版

某宝同款度盘不限速后台系统源码&#xff0c;验证已被我去除&#xff0c;两个后端系统&#xff0c;账号和卡密系统 第一步安装宝塔&#xff0c;部署卡密系统&#xff0c;需要环境php7.4 把源码丢进去&#xff0c;设置php7.4&#xff0c;和伪静态为thinkphp直接访问安装就行 …

qt 如何制作动态库插件

首先 首先第一点要确定我们的接口是固定的&#xff0c;也就是要确定 #ifndef RTSPPLUGIN_H #define RTSPPLUGIN_H #include "rtspplugin_global.h" typedef void (*func_callback)(uint8_t* data,int len,uint32_t ssrc,uint32_t ts,const char* ipfrom,uint16_t f…

【前端学习笔记】CSS基础一

一、什么是CSS 1.CSS 介绍 CSS&#xff08;Cascading Style Sheets&#xff0c;层叠样式表&#xff09;是一种用来控制网页布局和设计外观的样式语言。它使得开发者可以分离网页的内容&#xff08;HTML&#xff09;和表现形式&#xff08;样式&#xff09;&#xff0c;提高了…

Spring Security 介绍

1.概要 Spring Security是一个用于在Java应用程序中实现身份验证和访问控制的强大框架。它可以轻松地集成到任何基于Spring的应用程序中&#xff0c;提供了一套丰富的功能来保护应用程序的安全性。 https://spring.io/projects/spring-security/ demo:https://docs.spring.i…

unity2D游戏开发02添加组件移动玩家

添加组件 给PlayGame和EnemyObject添加组件BoxCollider 2D碰撞器&#xff0c;不用修改参数 给PlayGame添加组件Rigibody 2D 设置数据 添加EnemyObject&#xff0c;属性如下 Edit->project setting->Physics 2D 将 y的值改为0 给playerObject添加标签 新建层 将PlayerObj…

主从DNS服务器

实验 3 &#xff1a;主从 DNS 服务器 将一个区域文件复制到多个服务器上的过程叫做区域传送。将主服务器上的信息复制到辅助服务器上来 实现。 &#xff08; 1 &#xff09;完全区域传送&#xff1a;复制整个区域文件 查看日志 # 主 DNS 服务器的配置【主 dns 服务器的 ip 地…

【STM32 HAL库】ADC

ADC&#xff0c;顾名思义就是模拟信号->数字信号ADC工作原理 分类&#xff1a; 并联比较型-----转换速度快-----成本高、功耗高、分辨率低 分压部分比较部分编码部分&#xff08;其中Vx为模拟电压输入 &#xff09; 逐次逼近型-----结构简单&#xff0c;功耗低-----转换速…

C++STL详解(三)——vector类的接口详解

目录 一.vector的介绍 二.vector的构造以及赋值 2.1构造函数 2.2operator重载 三.vector的空间操作 3.1capacity和size函数 3.2reserve和resize函数 3.3empty函数 四.vector迭代器相关函数 4.1begin和end函数 4.2rbegin和rend函数 五.vector的增删查改 5.1push_back和…

QT串口和数据库通信

创建串口 串口连接客户端并向服务器发送消息 client.pro #------------------------------------------------- # # Project created by QtCreator 2024-07-02T14:11:20 # #-------------------------------------------------QT core gui network QT core gui…

【React】箭头函数:现代 JavaScript 的高效编程方式

文章目录 一、箭头函数的基本语法二、箭头函数的特性三、在 React 中的常见用法四、最佳实践 在现代 JavaScript 中&#xff0c;箭头函数&#xff08;Arrow Functions&#xff09;是一种简洁的函数表达方式&#xff0c;并且在 React 开发中非常常见。箭头函数不仅简化了函数的语…

RockyLinux 9 PXE Server bios+uefi 自动化部署 RockLinux 8 9

pxe server 前言 PXE&#xff08;Preboot eXecution Environment&#xff0c;预启动执行环境&#xff09;是一种网络启动协议&#xff0c;允许计算机通过网络启动而不是使用本地硬盘。PXE服务器是实现这一功能的服务器&#xff0c;它提供了启动镜像和引导加载程序&#xff0c;…

前端开发知识(三)-javascript

javascript是一门跨平台、面向对象的脚本语言。 一、引入方式 1.内部脚本&#xff1a;使用<script> &#xff0c;可以放在任意位置&#xff0c;也可以有多个&#xff0c;一般是放在<body></body>的下方。 2.外部脚本&#xff1a;单独编写.js文件&#xff…

HarmonyOS实现跨语言交互(Node-API)

Node-API简介 通过Native接口&#xff0c;实现两种代码的交互。 是在Node.js提供的Node-API基础上扩展而来&#xff0c;但与Node.js中的Node-API不完全兼容。本质就是提供了对C/C代码的使用接口&#xff0c;使得两种代码共同工作。规范I/O、CPU密集型、OS底层等能力。 应用场景…