详解—C++[智能指针]

目录

一、为什么需要智能指针?

二. 内存泄漏

2.1 什么是内存泄漏,内存泄漏的危害

2.2 内存泄漏分类

1.堆内存泄漏(Heap leak)

2.系统资源泄漏

2.3如何避免内存泄漏

三.智能指针的使用及原理

3.1 RAII

3.2 智能指针的原理

3.3 std::auto_ptr

3.4 std::unique_ptr

3.5 std::shared_ptr


一、为什么需要智能指针?

下面我们先分析一下下面这段程序有没有什么内存方面的问题?
 

#include <vector>
void _MergeSort(int* a, int left, int right, int* tmp)
{if (left >= right) return;int mid = left + ((right - left) >> 1);// [left, mid]// [mid+1, right]_MergeSort(a, left, mid, tmp);_MergeSort(a, mid + 1, right, tmp);int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;int index = left;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2])tmp[index++] = a[begin1++];elsetmp[index++] = a[begin2++];}while (begin1 <= end1)tmp[index++] = a[begin1++];while (begin2 <= end2)tmp[index++] = a[begin2++];memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
}
void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);_MergeSort(a, 0, n - 1, tmp);// 这里假设处理了一些其他逻辑vector<int> v(1000000000, 10);// ...// free(tmp);
}
int main()
{int a[5] = { 4, 5, 2, 3, 1 };MergeSort(a, 5);return 0;
}

问题分析:上面的问题分析出来我们发现有以下两个问题?

1. malloc出来的空间,没有进行释放,存在内存泄漏的问题。
2. 异常安全问题。如果在malloc和free之间如果存在抛异常,那么还是有内存泄漏。这种问题就叫异常安全。

二. 内存泄漏

2.1 什么是内存泄漏,内存泄漏的危害

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

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

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;
}

2.2 内存泄漏分类

C/C++程序中一般我们关心两种方面的内存泄漏:

1.堆内存泄漏(Heap leak)

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

2.系统资源泄漏

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

2.3如何避免内存泄漏

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


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


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


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

总结一下:

内存泄漏非常常见,解决方案分为两种:

1、事前预防型。如智能指针等。

2、事后查错型。如泄漏检测工具。

三.智能指针的使用及原理

3.1 RAII

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

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

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

// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
public:SmartPtr(T* ptr = nullptr): _ptr(ptr){}~SmartPtr(){if (_ptr)delete _ptr;}
private:T* _ptr;
};
void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);// 讲tmp指针委托给了sp对象,用时老师的话说给tmp指针找了一个可怕的女朋友!天天管着你,直到你go die^^SmartPtr<int> sp(tmp);// _MergeSort(a, 0, n - 1, tmp);// 这里假设处理了一些其他逻辑vector<int> v(1000000000, 10);// ...
}
int main()
{try {int a[5] = { 4, 5, 2, 3, 1 };MergeSort(a, 5);}catch (const exception& e){cout << e.what() << endl;}return 0;
}

3.2 智能指针的原理

上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容,因此:AutoPtr模板类中还得需要将* 、->重载下,才可让其像指针一样去使用。

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;
};
struct Date
{int _year;int _month;int _day;
};
int main()
{SmartPtr<int> sp1(new int);*sp1 = 10cout << *sp1 << endl;SmartPtr<int> sparray(new Date);// 需要注意的是这里应该是sparray.operator->()->_year = 2018;// 本来应该是sparray->->_year这里语法上为了可读性,省略了一个->sparray->_year = 2018;sparray->_month = 1;sparray->_day = 1;
}

3.3 std::auto_ptr


auto_ptr - C++ Reference (cplusplus.com)

C++98版本的库中就提供了auto_ptr的智能指针。下面演示的auto_ptr的使用及问题。

// C++库中的智能指针都定义在memory这个头文件中
#include <memory>
class Date
{
public:Date() { cout << "Date()" << endl; }~Date() { cout << "~Date()" << endl; }int _year;int _month;int _day;
};
int main()
{auto_ptr<Date> ap(new Date);auto_ptr<Date> copy(ap);// auto_ptr的问题:当对象拷贝或者赋值后,前面的对象就悬空了// C++98中设计的auto_ptr问题是非常明显的,所以实际中很多公司明确规定了不能使用auto_ptrap->_year = 2018;return 0;
}

auto_ptr的实现原理:管理权转移的思想,下面简化模拟实现了一份AutoPtr来了解它的原理

// 模拟实现一份简答的AutoPtr,了解原理
template<class T>
class AutoPtr
{
public:AutoPtr(T* ptr = NULL): _ptr(ptr){}~AutoPtr(){if (_ptr)delete _ptr;}// 一旦发生拷贝,就将ap中资源转移到当前对象中,然后另ap与其所管理资源断开联系,// 这样就解决了一块空间被多个对象使用而造成程序奔溃问题AutoPtr(AutoPtr<T>& ap): _ptr(ap._ptr){ap._ptr = NULL;}AutoPtr<T>& operator=(AutoPtr<T>& ap){// 检测是否为自己给自己赋值if (this != &ap){// 释放当前对象中资源if (_ptr)delete _ptr;// 转移ap中资源到当前对象中_ptr = ap._ptr;ap._ptr = NULL;}return *this;}T& operator*() { return *_ptr; }T* operator->() { return _ptr; }
private:T* _ptr;
};
int main()
{AutoPtr<Date> ap(new Date);// 现在再从实现原理层来分析会发现,这里拷贝后把ap对象的指针赋空了,导致ap对象悬空// 通过ap对象访问资源时就会出现问题。AutoPtr<Date> copy(ap);ap->_year = 2018;return 0;
}

3.4 std::unique_ptr

C++11中开始提供更靠谱的unique_ptr

unique_ptr - C++ Reference (cplusplus.com)

int main()
{unique_ptr<Date> up(new Date);// unique_ptr的设计思路非常的粗暴-防拷贝,也就是不让拷贝和赋值。unique_ptr<Date> copy(ap);return 0;
}

unique_ptr的实现原理:简单粗暴的防拷贝,下面简化模拟实现了一份UniquePtr来了解它的原理

// 模拟实现一份简答的UniquePtr,了解原理
template<class T>
class UniquePtr
{
public:UniquePtr(T* ptr = nullptr): _ptr(ptr){}~UniquePtr(){if (_ptr)delete _ptr;}T& operator*() { return *_ptr; }T* operator->() { return _ptr; }
private:// C++98防拷贝的方式:只声明不实现+声明成私有UniquePtr(UniquePtr<T> const&);UniquePtr& operator=(UniquePtr<T> const&);// C++11防拷贝的方式:deleteUniquePtr(UniquePtr<T> const&) = delete;UniquePtr& operator=(UniquePtr<T> const&) = delete;
private:T* _ptr;
};

3.5 std::shared_ptr

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

shared_ptr - C++ Reference (cplusplus.com)

int main()
{// shared_ptr通过引用计数支持智能指针对象的拷贝shared_ptr<Date> sp(new Date);shared_ptr<Date> copy(sp);cout << "ref count:" << sp.use_count() << endl;cout << "ref count:" << copy.use_count() << endl;return 0;
}

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

例如:比特老师晚上在下班之前都会通知,让最后走的学生记得把门锁下。

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

// 模拟实现一份简答的SharedPtr,了解原理
#include <thread>
#include <mutex>
template <class T>
class SharedPtr
{
public:SharedPtr(T* ptr = nullptr): _ptr(ptr), _pRefCount(new int(1)), _pMutex(new mutex){}~SharedPtr() { Release(); }SharedPtr(const SharedPtr<T>& sp): _ptr(sp._ptr), _pRefCount(sp._pRefCount), _pMutex(sp._pMutex){AddRefCount();}// sp1 = sp2SharedPtr<T>& operator=(const SharedPtr<T>& sp){//if (this != &sp)if (_ptr != sp._ptr){// 释放管理的旧资源Release();// 共享管理新对象的资源,并增加引用计数_ptr = sp._ptr;_pRefCount = sp._pRefCount;_pMutex = sp._pMutex;AddRefCount();}return *this;}T& operator*() { return *_ptr; }T* operator->() { return _ptr; }int UseCount() { return *_pRefCount; }T* Get() { return _ptr; }void AddRefCount(){// 加锁或者使用加1的原子操作_pMutex->lock();++(*_pRefCount);_pMutex->unlock();}
private:void Release(){bool deleteflag = false;// 引用计数减1,如果减到0,则释放资源_pMutex.lock();if (--(*_pRefCount) == 0){delete _ptr;delete _pRefCount;deleteflag = true;}_pMutex.unlock();if (deleteflag == true)delete _pMutex;}
private:int* _pRefCount; // 引用计数T* _ptr; // 指向管理资源的指针mutex* _pMutex; // 互斥锁
};
int main()
{SharedPtr<int> sp1(new int(10));SharedPtr<int> sp2(sp1);*sp2 = 20;cout << sp1.UseCount() << endl;cout << sp2.UseCount() << endl;SharedPtr<int> sp3(new int(10));sp2 = sp3;cout << sp1.UseCount() << endl;cout << sp2.UseCount() << endl;cout << sp3.UseCount() << endl;sp1 = sp3;cout << sp1.UseCount() << endl;cout << sp2.UseCount() << endl;cout << sp3.UseCount() << endl;return 0;
}

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

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

相关文章

代码随想录算法训练营 | day60 单调栈 84.柱状图中最大的矩形

刷题 84.柱状图中最大的矩形 题目链接 | 文章讲解 | 视频讲解 题目&#xff1a;给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 1 < heights.len…

PHP调用淘宝/拼多多电商平台商品详情API接口的方法及实现

随着互联网、云计算和大数据时代的到来&#xff0c;越来越多的应用程序需要调用第三方的API接口来获取数据&#xff0c;实现数据互通和协同工作。PHP作为一种常用的服务器端语言&#xff0c;也可以通过调用API接口来实现不同系统的数据交互和整合。本文将介绍PHP调用API接口的方…

第六部分 集合论

目录 主要内容 集合的基本概念 集合的基本运算 集合恒等式 初级运算 文氏图 集合的广义并与广义交 广义运算的性质 例1 A{{a},{a,b}} 集合算律 例2 判断下列命题是否为真 例3 设 例4 判断以下命题的真假&#xff0c;并说明理由. 解题思路 主要内容 集合的基本概念 属于、包含…

60 贪心算法解优势洗牌-田忌赛马问题

问题描述&#xff1a;给定两个大小相等的数组A和B&#xff0c;A相对于B的优势是A[i]>B[i]的缩影数目来确定&#xff0c;返回A的序列&#xff0c;使得其相对于B的优势最大化。 贪心算法求解&#xff1a;如果当前田即最快的马没有其最快的马快&#xff0c;那就用田最慢的马与…

59 贪心算法和回溯算法分割平衡字符串

问题描述&#xff1a;在一个平衡字符串中&#xff0c;L和R字符的数量是相同的。在给定一个平衡字符串s&#xff0c;请你将它分割成尽可能多的平衡字符串。注意:分割得到的每个字符串都必须是平衡字符串&#xff0c;返回可以通过分割得到的平衡字符串的最大数量。回溯算法求解&a…

JavaOOP篇----第十五篇

系列文章目录 文章目录 系列文章目录前言一、有没有可能两个不相等的对象有相同的hashcode二、拷贝和浅拷贝的区别是什么?三、static都有哪些用法?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通…

【python数据分析】北京房租数据分析

北京租房数据分析 下图展示的北京链家租房的数据&#xff0c;请针对以下数据进行数据清洗及展开数据分析&#xff0c;具体要求&#xff1a; 运用数据清洗等常规方法对数据进行预处理&#xff1b;针对出租屋不同的户型、房屋面积、房屋所属区域分别进行租金分析&#xff0c;并用…

HarmonyOS4.0系统性深入开发03UIAbility组件详解(中)

UIAbility组件基本用法 UIAbility组件的基本用法包括&#xff1a;指定UIAbility的启动页面以及获取UIAbility的上下文UIAbilityContext。 指定UIAbility的启动页面 应用中的UIAbility在启动过程中&#xff0c;需要指定启动页面&#xff0c;否则应用启动后会因为没有默认加载…

【封坛十年大典背后的故事无标题】

封坛十年大典背后的故事 这是2012年的春天。三个创业者吴海、王永吉、张明站在贫瘠裸露的五分梁上举目四望&#xff1a;烈日炎炎&#xff0c;土地干裂&#xff0c;卵石遍野。这时趴在乱石群的里的小草蔫黄&#xff0c;急切的盼望着一场季雨的唰唰唰&#xff01;地降落&#xff…

朴素贝叶斯法_naive_Bayes

朴素贝叶斯法&#xff08;naive Bayes&#xff09;是基于贝叶斯定理与特征条件独立假设的分类方法。对于给定的训练数据集&#xff0c;首先基于特征条件独立假设学习输入输出的联合概率分布&#xff1b;然后基于此模型&#xff0c;对给定的输入 x x x&#xff0c;利用贝叶斯定理…

设计模式之-单列设计模式,5种单例设计模式使用场景以及它们的优缺点

系列文章目录 设计模式之-6大设计原则简单易懂的理解以及它们的适用场景和代码示列 设计模式之-单列设计模式&#xff0c;5种单例设计模式使用场景以及它们的优缺点 设计模式之-3种常见的工厂模式简单工厂模式、工厂方法模式和抽象工厂模式&#xff0c;每一种模式的概念、使用…

抖音网红罗盘时钟改良版

文章目录 💕效果展示💕代码展示HTML💕效果展示 💕代码展示 HTML <!DOCTYPE html> <html lang=

61 贪心算法解救生艇问题

问题描述&#xff1a;第i个人的体重为peaple[i],每个船可以承载的最大重量为limit。每艘船最多可以同时载两人&#xff0c;但条件是这些人的重量之和最多为limit&#xff0c;返回载到每一个人多虚的最小船数&#xff0c;(保证每个人被船载)。 贪心算法求解&#xff1a;先将数组…

【BBuf的CUDA笔记】十,Linear Attention的cuda kernel实现解析

欢迎来 https://github.com/BBuf/how-to-optim-algorithm-in-cuda 踩一踩。 0x0. 问题引入 Linear Attention的论文如下&#xff1a; Transformers are RNNs: Fast Autoregressive Transformers with Linear Attention&#xff1a;https://arxiv.org/pdf/2006.16236.pdf 。官方…

微信小程序-textarea组件字数实时更新

一、前言 本文实现的是在小程序中&#xff0c;textarea文本框输入文字后&#xff0c;实时显示文字的字数&#xff0c;获取更好的用户输入体验以及提示。 下图是实现的效果 二、代码实现 2-1、wxml代码 <view style"padding: 30rpx;"><view style"…

MySQL 数据库系列课程 04:MySQL Workbench的安装

Workbench 是 MySQL 官方推出的免费的强大的可视化工具&#xff0c;不熟悉命令行工具的人&#xff0c;可以安装这一款软件&#xff0c;通过编写 SQL 进行数据库中数据的增删改查操作&#xff0c;接下来我们详细说明一下 Workbench 的安装。 一、Windows安装Workbench &#x…

minicube搭建golang容器服务

引言 最近在自己电脑上搭建一个小型k8s环境&#xff0c;以学习云原生相关内容。这里我主要分为三部分记录&#xff1a; 容器及容器编排理论环境安装相关rpcx服务实战 还在调试中&#xff0c;先总结整理下&#xff0c;这里后续补充上我的github工程链接。 一、容器及容器编排理…

java八股 redis

Redis篇-01-redis开篇_哔哩哔哩_bilibili 1.缓存穿透 2.缓存击穿 逻辑过期里的互斥锁是为了保证只有一个线程去缓存重建 3.缓存雪崩 4.双写一致性 4.1要求一致性&#xff08;延迟双删/互斥锁&#xff09; 延迟双删无法保证强一致性 那么前两步删缓和更新数据库哪个先呢&#xf…

基于Java SSM框架实现实现定西扶贫惠农推介志愿者系统项目【项目源码+论文说明】

基于java的SSM框架实现定西扶贫惠农推介志愿者系统演示 摘要 扶贫工作是党中央、国务院的一项重要战略部署。党政机关定点扶贫是中国扶贫开发战略部署的重要组成部分&#xff0c;是新阶段扶贫开发的一项重大举措&#xff0c;对推动贫困地区经济社会的发展有着积极的意义。 本…

Linux命令的操作练习

1.创建ss别名&#xff0c;查看长格式详细信息 alias ssls -l 2.创建ss别名&#xff0c;复制boot文件夹下的内容到data文件夹下 alias sscp -r /boot /data 3.删除别名ss unalias ss 4. 复制test文件夹下的passwd文件到qq文件夹下&#xff0c;并改名为ww cp test/pas…