防止内存泄漏的神兵利器 — 智能指针

news2024/9/22 11:32:29

1.内存泄漏

1.1什么是内存泄漏

当我们在写C/C++程序的时候,难免会出现内存泄漏的错误,因为C/C++不像Java语言那样,拥有自己的垃圾回收机制,C/C++中对于资源的管理,完全交给程序员自己打理,也就是说使用C/C++的程序员可以直接和内存打交道,写出来的程序效率自然比其他语言的运行速度更快,这是C++的优点,但同样也是C++的缺点,因为,我们难以保证我们是否正确释放了不在使用的资源。比如:当我们因为疏忽大意而忘记释放不在使用的程序;又或者是我们记得释放不再使用的资源,但,因为程序中的执行流乱跳而导致程序没有执行到释放资源的代码;这个时候就会造成内存泄漏。内存泄漏简单来说就是 未释放不再使用的内存资源。

内存是一种有限的资源,使用完之后放回原处(还给操作系统),当程序中其他地方还需要使用的时候,直接向操作系统申请即可;但是,如果使用完之后没有放回原处(未还给操作系统),当其他程序向操作系统申请内存空间的时候,操作系统就会左拼右凑给该程序分配一块内存空间,但是当有非常巨大的内存空间没有还给操作系统时,操作系统就会很尴尬的说,“不好意思,没有,哪个谁谁谁还没还给我呢”,这个时候就会造成程序运行缓慢,严重的话还会造成程序卡死。所以我们应当避免内存泄漏。

1.2如何防止内存泄漏

通常来说,我们申请的内存资源,在不使用的时候记得释放即可,类似于一下代码

int* func()
{
	int* ptr = new int[10]; // 申请资源
	return ptr;
}

int main()
{
	int* ptr = func(); 
	delete[] ptr;      // 释放资源

	return 0;
}

但是总有一些特殊情况,比如在使用异常的时候,当捕获异常之后,程序执行流直接跳转到匹配的catch语句块中执行,如果在这之间跳过了释放资源的代码语句,就会造成资源泄漏问题;如以下代码:

int divi(int a,int b)
{
	if (0 == b)
		throw "处零错误";
	
	return a / b;
}

void func()
{
	int* ptr = new int;
	divi(4,0);

	cout << "释放资源" << endl;
	delete ptr;
}

int main()
{
	try
	{
		func();
	}
	catch (...)
	{
		cout << "出现除零错误" << endl;
	}

	return 0;
}

可以看出,当出现除0错误的时候,程序跳过了释放资源的语句,造成程序泄漏,所以光记得释放资源也不一定能避免内存泄漏问题,这个时候,C++的前辈们就引入了新的机制,通过智能指针来管理资源。

2.RAII和智能指针的关系

RAII(Resource Acquisition Is Initialization)是一种编程技术,即资源获得即初始化;智能指针是利用这个技术所实现的具体产物;所以学习智能指针之前,很有必要了解一下RAII的思想;RAII的核心思想是将资源的获取(初始化)与对象的构造绑定,将资源的释放与对象的析构绑定,从而把管理一份资源的责任托管给一个对象; 利用C++的作用域和析构函数的特性来自动管理资源确保资源在不再需要时能够被自动释放,从而避免了资源泄漏和其他资源管理错误。

我们可以类比于局部的临时变量来理解,临时变量只在其作用域有效,当出了作用域就销毁了,这是在栈区上开辟资源的特性;但是我们申请的资源是在堆区的,堆区上的资源 生命周期是随进程的,不会像栈区上的资源那样自动释放,那如果通过栈区上的对象来管理堆区上的资源,是不是就可以保证资源的自动销毁呢?没错,这就是实现智能指针的思想。

RAII的简单代码如下:

template <typename T>
class RAII_test
{
public:
	smart_ptr(T* ptr)
		:_ptr(ptr) // 资源的获取与对象的构造函数绑定
	{}

	~smart_ptr()   // 资源的释放与对象的析构函数绑定
	{
		delete _ptr;
	}
private:
	T* _ptr;
};

上述代码只是展示一下RAII思想,还不能称为智能指针,因为它还不能像指针一样使用;要想实现智能指针,还需要使其具有指针的行为,如:解引用,通过箭头访问。简单的智能指针代码如下:

template<class T>
class SmartPtr
{
public:
	// 1.RAII
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}
	~SmartPtr()
	{
		cout << "delete:" << _ptr << endl;
		delete _ptr;
	}
	// 2.像指针一样使用
	T& operator*() {return *_ptr;}
	T* operator->() {return _ptr;}

private:
	T* _ptr;
};

3.C++标准库中的智能指针

3.1标准库中智能指针简介

智能指针可谓是解决内存泄漏的神兵利器,C++标准库中提供了四种智能指针,分别是auto_ptr、unique_ptr、shared_ptr、weak_ptr;这时,你就应该有一个大大的疑问了,为什么C++的标准库中要提供四种智能指针呢?提供一个不就好了吗,毕竟他们都是进行管理资源的。这其实和智能指针的拷贝有关。

智能指针之间的拷贝,不同于string,vector这些类的拷贝,这些类拷贝完之后,我们希望拷贝出来的对象拥有自己的资源,所以是深拷贝。而智能指针拷贝完成之后,我们希望拷贝的智能指针应该指向原来的资源,而不是指向自己独有的资源,所以智能指针之间的拷贝应该是浅;但是这就会造成析构两次的问题,第一次析构正常析构,第二次析构的指针就变成野指针了,析构野指针,程序崩溃。所以为了解决智能指针之间的拷贝问题,标准库中提供了四个智能指针。

各个智能指针解决拷贝问题的思想:

        auto_ptr:auto_ptr的实现思想是 管理权转移;但是auto_ptr很坑,会导致被拷贝对象置空,一般不建议使用。

        unique_ptr:unique_ptr的实现思想是 禁止拷贝;适用于不需要拷贝的场景。

        shared_ptr:shared_ptr的实现思想是 通过引用计数来管理资源的释放;允许自由拷贝,但是使用的时候要注意避免循环引用的问题。

        weak_ptr:weak_ptr主要 用来解决shared_ptr中的循环引用问题

auto_ptr和unique_ptr比较简单,下面主要讲解一下shared_ptr。

3.2shared_ptr的引用计数的实现

我们知道shared_ptr是通过引用计数来支持拷贝的,抱着 “知其然,知其所以然” 的态度,我们一起来了解一下shared_ptr是如何通过引用计数来解决对象之间的拷贝问题。

大体思想就是一份资源配一个引用计数,无论多少个对象管理这份资源,都只有一个引用计数。具体实现就是,每个对象存一个找的这个引用计数的指针。如下图所示:​​​​​​​

shared_ptr具体实现可以参考下面这份简单的代码: 

template<class T>
class myshared_ptr
{
public:
	// RAII
	myshared_ptr(T* ptr = nullptr)
		:_ptr(ptr)
		, _pcount(new int(1))
	{}
	myshared_ptr(const myshared_ptr<T>& sp)
	{
		_ptr = sp._ptr;
		_pcount = sp._pcount;

		// 拷贝时++计数
		++(*_pcount);
	}
	myshared_ptr<T>& operator=(const myshared_ptr<T>& sp)
	{
		//if (this != &sp)
		if (_ptr != sp._ptr)
		{
			release();

			_ptr = sp._ptr;
			_pcount = sp._pcount;

			// 拷贝时++计数
			++(*_pcount);
		}

		return *this;
	}
	void release()
	{
		// 说明最后一个管理对象析构了,可以释放资源了
		if (--(*_pcount) == 0)
		{
			cout << "delete:" << _ptr << endl;
			delete _ptr;
			delete _pcount;
		}
	}
    // 析构时,--计数,计数减到0,
	~myshared_ptr(){release();}

	// 像指针一样
	T& operator*(){return *_ptr;}
	T* operator->(){return _ptr;}
private:
	T* _ptr;
	int* _pcount;
};

3.3shared_ptr的循环引用问题

shared_ptr千般好万般好,但是在特殊场景下,也会存在缺陷,如以下代码:

struct Node
{
	int _val;
	myshared_ptr<Node> _next;
	myshared_ptr<Node> _prev;

	Node(int val = 0)
		:_val(val)
	{}

	~Node()
	{
		cout << "~Node()" << endl;
	}
};

int main()
{
	myshared_ptr<Node> ptr1(new Node(1));
	myshared_ptr<Node> ptr2(new Node(2));

	ptr1->_next = ptr2;
	ptr2->_prev = ptr1;

	return 0;
}

 上面代码的场景如下图:

分析上述场景可知,该场景为有两个结点,结点中的myshared_ptr互相指向对方;析构的时候,ptr2指向的结点先析构,因为ptr1中的成员变量_next指向ptr2所指向的结点,所以引用计数减到1,右侧节点不会释放;析构ptr1指向的结点时,因为ptr2的成员变量_prev指向当前节点,引用计数减到1,左侧结点也不会释放。最终造成两个结点都不会释放,这就是shared_ptr在该场景下的循环引用问题。

为了解决循环引用问题,C++标准库提供了weak_ptr,将定义结点的代码改为下面这份代码即可解决问题:

struct Node
{
	int _val;
	std::weak_ptr<Node> _next;
	std::weak_ptr<Node> _prev;

	Node(int val = 0)
		:_val(val)
	{}

	~Node()
	{
		cout << "~Node()" << endl;
	}
};

当使用weak_ptr时,在该场景下,ptr1和ptr2所指向的结点的引用计数,不会因为对方的成员变量weak_ptr类型的对象指向彼此而增加。使用weak_ptr后场景如下:

当对方中的_prev和_next是weak_ptr时,引用计数不会增加,析构的时候,ptr2先析构,引用计数减到0,析构ptr2指向的结点;ptr1再析构,同理,引用计数减到0,析构ptr1所指向的结点;最终,两个结点都析构了。

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

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

相关文章

Ubuntu下NFS和SSH服务

本篇文章记录Ubuntu下如何对NFS和SSH服务进行配置和开启。 目录 一、NFS服务 二、SSH服务 1、安装SSH服务 2. 启动和检查SSH服务 3. 配置SSH服务 4. 连接到SSH服务 5. 设置防火墙 6. 测试连接 三、结语 一、NFS服务 NFS&#xff08;Network File System&#xff0…

设计模式(2)行为型模式和七大原则

1、目标 本文的主要目标是学习设计模式的行为型模式并举例说明 2、行为型模式 2.1 观察者模式&#xff08;Observer&#xff09; 观察者模式是对象之间存在一对多的依赖关系&#xff0c;当一个对象的状态发生变化时&#xff0c;所有依赖它的对象都会得到通知并自动更新&…

京东数据编织

计算引擎是Hbase 中间计算结果的物化【就是存下来】 自动物化 在这里插入图片描述

设计模式在芯片验证中的应用——状态

一、状态模式 状态模式是一种行为设计模式&#xff0c; 让你能在一个对象的内部状态变化时改变其行为&#xff0c; 使其看上去就像改变了自身所属的类一样。 在RTL中可能存在复杂的有限状态机FSM&#xff0c;在任何一个特定状态中&#xff0c; RTL的行为都不相同&#xff0c;…

pip install 遇到ValueError: check_hostname requires server_hostname的解决办法

我需要下载Cython来将py编译成c&#xff0c;结果在pip install的时候报错这个&#xff1a; ERROR: Exception: Traceback (most recent call last):File "F:\Anaconda3\envs\DouyinLive32\lib\site-packages\pip\_internal\cli\base_command.py", line 173, in _mai…

《人类群星闪耀时》

人类群星闪耀时&#xff0c;历史的舞台上&#xff0c;你未尝不是其中一颗。 【拜占庭的沦陷】具备决断力、创新力、执行力的领导者起到关键作用。 【享德尔的复活】人的心力一旦强大&#xff0c;便可创造非凡之事。 【一夜天才】闪耀的星离不开黑夜的衬托。所谓的英雄&#x…

HarmonyOS Developer之生成二维码

qrcode 生成并显示二维码 属性 样式 创建qrcode组件 在pages/index目录下的hml文件中创建一个qrcode组件 HTML <!-- xxx.hml--> <div class"container"><qrcode value"Hello"></qrcode> </div>CSS /* xxx.css */ .cont…

uniapp 微信小程序生成水印图片

效果 源码 <template><view style"overflow: hidden;"><camera device-position"back" flash"auto" class"camera"><cover-view class"text-white padding water-mark"><cover-view class"…

基于JAVA的社团管理系统的设计与实现

TOC springboot270基于JAVA的社团管理系统的设计与实现 第1章 绪论 1.1 课题背景 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供…

html+css 实现hover 边框彩色按钮

前言:哈喽,大家好,今天给大家分享html+css 绚丽效果!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 一、效果二、原理解析1.这是一个,hover后按钮边框==变彩色==的效果。每个按钮都是1个but…

matlab关于结构体的创建

网上搜了很多结构体的创建&#xff0c;都是什么student.name student.age,或者struct. 自己摸索了一下&#xff0c;根据你想要创建结构体的格式可以划分以下两类 clear all clcdata [1, 2, 2, 1;2, 1, 1, 3;4, 3, 2, 2];data1 [0, 2, 2, 1;0, 1, 1, 3;4, 3, 0, 2;4, 3, 0, 2…

解锁企业数字化转型的终极指南:《数字时代的敏捷架构》深度解读

在当前数字化浪潮的冲击下&#xff0c;企业面临着前所未有的挑战与机遇。为了解决这一难题&#xff0c;The Open Group 和 AZone 联手推出了《数字时代的敏捷架构》&#xff0c;《数字时代的敏捷架构》汇集了全球顶尖专家的智慧和经验&#xff0c;是企业数字化转型的必读之作。…

【IEEE独立出版】第四届人工智能、虚拟现实与可视化国际学术会议(AIVRV 2024)

第四届人工智能、虚拟现实与可视化国际学术会议&#xff08;AIVRV 2024&#xff09; 2024 4th International Conference on Artificial Intelligence, Virtual Reality and Visualization 会议时间&#xff1a;2024年11月1日-3日 会议地点&#xff1a;中国-南京…

C语言第20天笔记

文件操作 概述 什么是 文件 文件时保存在外存储器上&#xff08;一般代指磁盘&#xff0c;也可以是U盘、移动硬盘等&#xff09;的数据的集合。 文件操作体现在哪几个方面 1. 文件内容的读取 2. 文件内容的写入 数据的读取和写入可被视为针对文件进行输入和输出的操作&a…

函数递归那些事

什么是递归 递归就是函数自己调用自己&#xff0c;而递归的本质其实是一种解决问题的方法。 递归的思想 递归的思想是把复杂问题大事化小的过程。即把一个大型复杂的问题不断的拆分成与原问题相似&#xff0c;但规模较小的子问题&#xff0c;直到子问题不能被拆分&#xff0…

Python学习day16-类与对象

这里写目录标题 类示例 成员方法self关键字 类与对象构造方法其他类内置方法&#xff08;魔术方法&#xff09;_str_符号_Lt_符号le小于等于比较eq比较运算小结 类 在Python中&#xff0c;class&#xff08;类&#xff09;是一种用于创建对象的模板或蓝图。它封装了数据&#…

MySQL9.0安装教程

软件介绍 MySQL是一个关系型数据库管理系统&#xff0c;是最流行的关系型数据库管理系统之一&#xff0c;在 WEB 应用方面&#xff0c;MySQL是最好的 RDBMS (Relational Database Management System&#xff0c;关系数据库管理系统) 应用软件之一。 软件下载 https://pan.qu…

预报名管理系统--论文pf

TOC springboot374预报名管理系统--论文pf 第1章 绪论 1.1选题动因 当前的网络技术&#xff0c;软件技术等都具备成熟的理论基础&#xff0c;市场上也出现各种技术开发的软件&#xff0c;这些软件都被用于各个领域&#xff0c;包括生活和工作的领域。随着电脑和笔记本的广泛…

UI设计:蒸汽波风格页面有啥特征,应用哪些场景?

一、什么是蒸汽波风格 蒸汽波风格&#xff08;Steampunk&#xff09;是一种将19世纪工业时代的技术和想象力与未来科技相结合的艺术和文化流派。它通常描绘了一个类似维多利亚时代的世界&#xff0c;其中蒸汽动力是主要能源&#xff0c;机械装置和复杂的齿轮系统被广泛应用。 …

C#商城源码与.NET技术在电商领域的应用_OctShop

在当今互联网化商业的浪潮中&#xff0c;网上商城成为了企业拓展市场、提升竞争力的重要手段。而 C# 商城源码和.NET 相关的技术在构建高效、稳定、安全的网上商城中发挥着关键作用。OctShop将深入探讨 C# 商城源码、.NET 商城源码、C# 网上商城以及.NET Core 商城源码的特点、…