C++之C++11新特性(三)--- 智能指针

news2024/12/1 4:07:36

目录

一、智能指针

1.1 为什么需要智能指针

1.2 内存泄漏

1.2.1 内存泄漏的基本概念

1.2.2 内存泄漏的分类

1.2.3 如何避免内存泄漏

1.3 智能指针的使用及其原理

1.3.1 RAII

1.3.2 智能指针的基本原理

1.3.3 auto_ptr

1.3.4 unique_ptr

1.3.5 shared_ptr

1.3.6 shared_ptr中的循环引用问题

1.3.7 定制删除器


一、智能指针

1.1 为什么需要智能指针

首先咱们来看一段代码:

int div()
{
    int a, b;
    cin >> a >> b;
    if (b == 0)
    throw invalid_argument("除0错误");
    return a / b;
}
void Func()
{
// 1、如果p1这里new 抛异常会如何?
// 2、如果p2这里new 抛异常会如何?
// 3、如果div调用这里又会抛异常会如何?
    int* p1 = new int;
    int* p2 = new int;
    cout << div() << endl;
    delete p1;
    delete p2;
}
int main()
{
    try
    {
    Func();
    }
    catch (exception& e)
    {
    cout << e.what() << endl;
    }
    return 0;
}

如果我们在div中b为0的话,我们的程序将会抛出异常,使得在Func函数cout之后的语句都不会执行,那就会导致p1,p2所指向的资源没有被及时的释放,就会导致资源泄露的问题,所以很明显,这里我们需要采取一些其他方法来管理我们的资源,在讲其他方法之前,先让我们来看看内存泄漏。

1.2 内存泄漏

1.2.1 内存泄漏的基本概念

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

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

1.2.2 内存泄漏的分类

  • 堆内存泄漏(Heap leak)
    堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
  • 系统资源泄漏
    指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

1.2.3 如何避免内存泄漏

  1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证。
  2. 采用RAII思想或者智能指针来管理资源。
  3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
  4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。

1.3 智能指针的使用及其原理

1.3.1 RAII

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

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

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

1.3.2 智能指针的基本原理

现在让我们根据RAII的基本思想来写一个简单的SmartPtr:

template <class T>
class SmartPtr {
	SmartPtr(T* ptr):_ptr(ptr)
	{}
	~SmartPtr()
	{
		if (_ptr != nullptr) delete _ptr;
	}

	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
};

这只是我简易实现的一个智能指针,库里面当然也有现成的智能指针,接下来就来让我们看看吧

1.3.3 auto_ptr

C++98版本的库中就提供了auto_ptr的智能指针。下面演示的auto_ptr的使用及问题。auto_ptr的实现原理:管理权转移的思想,下面简化模拟实现了一份bit::auto_ptr来了解它的原理.我们可以来看看其原理。

template <class T>
class my_auto_ptr {
	my_auto_ptr(T* ptr) :_ptr(ptr)
	{}
	~my_auto_ptr()
	{
		if (_ptr != nullptr) delete _ptr;
	}

	my_auto_ptr(my_auto_ptr<T>& sp)
		:_ptr(sp._ptr)
	{
		sp._ptr = nullptr;
	}

	T* operator=(my_auto_ptr<T>& ptr)
	{
		//检测是否给自己赋值
		if (this != &ptr)
		{
			if (_ptr) delete _ptr;
			_ptr = ptr._ptr;
			ptr._ptr = nullptr;
		}
		return *this;
	}

	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}
	private:
		T* _ptr;
};

但是呢,auto_ptr并不是一个优秀的设计,不建议使用

1.3.4 unique_ptr

unique_ptr的实现原理:简单粗暴的防拷贝,

下面简化模拟实现了一份UniquePtr来了解它的原理

template<class T>
class my_unique_ptr {
	my_unique_ptr(T* ptr) :_ptr(ptr)
	{}
	~my_unique_ptr()
	{
		if (_ptr != nullptr) delete _ptr;
	}

	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}

	my_unique_ptr(const my_unique_ptr<T>& ptr) = delete;
	my_unique_ptr<T>& operator=(const my_unique_ptr<T>& ptr) = delete;

private:
	T* _ptr;
};

1.3.5 shared_ptr

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。例如:老师晚上在放学之前都会通知,让最后走的学生记得把门锁下。

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

接下来同样先尝试模拟实现一下吧:

template<class T>
class my_shared_ptr {
	my_shared_ptr(T* ptr = nullptr) :_ptr(ptr),_pcount(new int(1))
	{}
	~my_shared_ptr()
	{
		Release();
	}

	void Release()
	{
		if (--(*_pcount) == 0)
		{
			delete _ptr;
			delete _pcount;
		}
	}

	void AddCount()
	{
		(*_pcount)++;
	}

	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}

	my_shared_ptr(const my_shared_ptr<T>& ptr)
	{
		_ptr = ptr._ptr;
		_pcount = ptr._pcount;
		AddCount();
	}
	
	my_shared_ptr<T>& operator=(const my_shared_ptr<T>& ptr)
	{
		if (_ptr != ptr._ptr)
		{
			Release();
			_ptr = ptr._ptr;
			_pcount = ptr._pcount;
			AddCount();
		}
		return *this;
	}

private:
	T* _ptr;
	int* _pcount;
};

这个版本的智能指针就是目前使用最为常见和广泛的了。

1.3.6 shared_ptr中的循环引用问题

这里用一张图来给大家说明一下这个问题,
先引入一段代码

struct ListNode
{
    int _data;
    shared_ptr<ListNode> _prev;
    shared_ptr<ListNode> _next;
    ~ListNode(){ 
    cout << "~ListNode()" << endl; 
    }
}

int main()
{
    shared_ptr<ListNode> node1(new ListNode);
    shared_ptr<ListNode> node2(new ListNode);
    cout << node1.use_count() << endl;
    cout << node2.use_count() << endl;
    node1->_next = node2;
    node2->_prev = node1;
    cout << node1.use_count() << endl;
    cout << node2.use_count() << endl;
    return 0;
}

这里需要提前说明的是,node1与node2都不会正常释放的,下面来说说为什么。

  1. node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动delete
  2. node1的_next指向node2,node2的_prev指向node1,引用计数变成2
  3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点
  4. 也就是说_next析构了,node2就释放了
  5. 也就是说_prev析构了,node1就释放了
  6. 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放

这就是循环引用问题,那么要怎么解决循环引用问题呢,在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了。

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

当然以上这种循环引用的情况,咱们能避免就避免,免得恶心自己。 

1.3.7 定制删除器

当我们遇到一些场景时,系统提供的默认删除器是解决不了问题的,例如:

shared_ptr<int> sp1(new int[10]);
shared_ptr<FILE> sp2(fopen("test.txt","r"));

像这样的场景,使用默认的删除器是肯定会报错的,但是在构造函数的地方可以传入一个定制
删除器,也就是一个函数对象,当我们有传入删除器时,系统就会使用我们所提供的删除器来释放我们的资源,例如,上述代码我们可以这样改写:

shared_ptr<int> sp1(new int[10],[](int* arr){ delete[] arr; });
shared_ptr<FILE> sp2(fopen("test.txt","r"),[](FILE* ptr){ fclose(ptr); });

当我们有了定制删除器后,我们的shared_ptr就可以帮助我们处理更多的场景。

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

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

相关文章

Flink学习连载文章8--时间语义

Time的分类 (时间语义) EventTime:事件(数据)时间,是事件/数据真真正正发生时/产生时的时间 IngestionTime:摄入时间,是事件/数据到达流处理系统的时间 ProcessingTime:处理时间,是事件/数据被处理/计算时的系统的时间 EventTime的重要性 假设&#xff0c;你正在去往地下停…

【Docker】部署nginx

docker部署nginx docker部署nginx镜像加速器1、拉取nginx镜像2、创建nginx容器3、浏览器访问 docker部署nginx 镜像加速器 备注&#xff1a;阿里云镜像加速地址 https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors可用的镜像源&#xff1a; https://https://reg…

github webhooks 实现网站自动更新

本文目录 Github Webhooks 介绍Webhooks 工作原理配置与验证应用云服务器通过 Webhook 自动部署网站实现复制私钥编写 webhook 接口Github 仓库配置 webhook以服务的形式运行 app.py Github Webhooks 介绍 Webhooks是GitHub提供的一种通知方式&#xff0c;当GitHub上发生特定事…

Rust SQLx CLI 同步迁移数据库

上文我们介绍了SQLx及SQLite&#xff0c;并介绍了如何使用代码同步迁移数据库。本文介绍Sqlx cli 命令行工具&#xff0c;介绍如何安装、使用&#xff0c;利用其提供的命令实现数据表同步迁移。Java生态中有flyway, sqlx cli 功能类似&#xff0c;利用命令行工具可以和其他语言…

论文笔记-WWW2024-ClickPrompt

论文笔记-WWW2024-ClickPrompt: CTR Models are Strong Prompt Generators for Adapting Language Models to CTR Prediction ClickPrompt: CTR模型是大模型适配CTR预测任务的强大提示生成器摘要1.引言2.预备知识2.1传统CTR预测2.2基于PLM的CTR预测 3.方法3.1概述3.2模态转换3.…

解析客服知识库搭建的五个必要性

在当今竞争激烈的商业环境中&#xff0c;客服知识库的搭建已成为企业提升服务质量、优化客户体验的重要手段。一个完善的客服知识库不仅能帮助企业高效管理客户服务流程&#xff0c;还能显著提升客户满意度和忠诚度。以下是搭建客服知识库的五个必要性&#xff1a; 1. 提升服务…

css—轮播图实现

一、背景 最近和朋友在一起讨论的时候&#xff0c;我们提出了这样的一个提问&#xff0c;难道轮播图的效果只能通过js来实现吗&#xff1f;经过我们的一系列的争论&#xff0c;发现了这是可以通过纯css来实现这一效果的&#xff0c;CSS轮播图也是一种常见的网页展示方式&#x…

40分钟学 Go 语言高并发:【实战课程】工作池(Worker Pool)实现

工作池&#xff08;Worker Pool&#xff09;实战实现 一、知识要点概述 模块核心功能实现难点重要程度池化设计管理协程生命周期并发安全、资源控制⭐⭐⭐⭐⭐动态扩缩容根据负载调整池大小平滑扩缩、性能优化⭐⭐⭐⭐任务分发合理分配任务到worker负载均衡、任务优先级⭐⭐⭐…

深度学习3:数据预处理使用Pandas与PyTorch的实践

文章目录 导读一、主题与提纲1.1. 读取数据集1.2. 处理缺失值1.3. 转换为张量格式二、结论本文是经过严格查阅相关权威文献和资料,形成的专业的可靠的内容。全文数据都有据可依,可回溯。特别申明:数据和资料已获得授权。本文内容,不涉及任何偏颇观点,用中立态度客观事实描…

004 MATLAB数值微积分

01 函数的极值点 求解一元函数在区间(x1,x2)中极小值点&#xff1a; xfminbnd(fun,x1,x2)求解初始向量为x0的多元函数极小值点x和对应的极值y [x,y]fminsearch(fun,x0)02 微积分 1.数值微分&#xff1a; 一次微分&#xff1a; diff(x) 若x是一个向量&#xff0c;则返回[x(…

重塑用户体验!快手电商智能巡检平台的实践与探索

导读&#xff1a;随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;已经成为推动各行各业创新的重要力量。特别是在用户体验方面&#xff0c;AI 技术的应用不仅解决了许多传统问题&#xff0c;还带来了全新的交互方式和更高的用户满意度。本文将从快手电商B端…

C# 结构体

文章目录 前言一、结构体的定义与基本使用&#xff08;一&#xff09;定义结构体&#xff08;二&#xff09;结构体的使用示例 二、C# 结构的特点&#xff08;一&#xff09;丰富的成员类型&#xff08;二&#xff09;构造函数相关限制与特性&#xff08;三&#xff09;继承方面…

实现Linux平台自定义协议族

一 简介 我们常常在Linux系统中编写socket接收TCP/UDP协议数据&#xff0c;大家有没有想过它怎么实现的&#xff0c;如果我们要实现socket接收自定义的协议数据又该怎么做呢&#xff1f;带着这个疑问&#xff0c;我们一起往下看吧~~ 二 Linux内核函数简介 在Linux系统中要想…

Asp.net core Autofac 案例 注入、AOP 启用接口代理拦截 启用 类代理拦截=== 只会拦截虚方法

资料 core 实现autofac 》》》 安装 如下工具包 安装之后 如出现 这种 》》》编写 AOP类 using Castle.DynamicProxy; using System.Diagnostics;namespace Web01.AOP {/// <summary>/// 日志记录/// </summary>public class LoggingInterceptor : IInterc…

【深度学习】各种卷积—卷积、反卷积、空洞卷积、可分离卷积、分组卷积

在全连接神经网络中&#xff0c;每个神经元都和上一层的所有神经元彼此连接&#xff0c;这会导致网络的参数量非常大&#xff0c;难以实现复杂数据的处理。为了改善这种情况&#xff0c;卷积神经网络应运而生。 一、卷积 在信号处理中&#xff0c;卷积被定义为一个函数经过翻转…

VOLO实战:使用VOLO实现图像分类任务(二)

文章目录 训练部分导入项目使用的库设置随机因子设置全局参数图像预处理与增强读取数据设置Loss设置模型设置优化器和学习率调整策略设置混合精度&#xff0c;DP多卡&#xff0c;EMA定义训练和验证函数训练函数验证函数调用训练和验证方法 运行以及结果查看测试完整的代码 在上…

GPU 服务器厂家:怎样铸就卓越 AI 算力?

文章来源于百家号&#xff1a;GPU服务器厂家 今天咱来聊聊 GPU 服务器厂家那些事儿&#xff0c;而这其中衡量 AI 算力的因素可是关键所在哦。 先讲讲计算速度这一块。咱都知道 AI 那复杂的活儿&#xff0c;像训练超厉害的图像识别模型&#xff0c;得处理海量图像数据&#x…

DroneCAN 最新开发进展,Andrew在Ardupilot开发者大会2024的演讲

本文是Andrew演讲的中文翻译&#xff0c;你可以直接观看视频了解演讲的全部内容&#xff0c;此演讲视频的中文版本已经发布在Ardupilot社区的Blog板块&#xff0c;你可以在 Arudpilot官网&#xff08;https://ardupilot.org) 获取该视频&#xff1a; 你也可以直接通过Bilibili链…

USB Type-C一线通扩展屏:多场景应用,重塑高效办公与极致娱乐体验

在追求高效与便捷的时代&#xff0c;启明智显USB Type-C一线通扩展屏方案正以其独特的优势&#xff0c;成为众多职场人士、娱乐爱好者和游戏玩家的首选。这款扩展屏不仅具备卓越的性能和广泛的兼容性&#xff0c;更能在多个应用场景中发挥出其独特的价值。 USB2.0显卡&#xff…

Android 混淆问题

我的安卓混淆只需要在gradle里面开启就行了。 buildTypes {release {minifyEnabled trueshrinkResources truezipAlignEnabled trueproguardFiles getDefaultProguardFile(proguard-android-optimize.txt), proguard-rules.pro}} minifyEnabled true 这个就是开启方法&#xf…