【C++进阶学习】第十三弹——C++智能指针的深入解析

news2024/12/24 10:25:06

前言:

在C++编程中,内存管理是至关重要的一个环节。传统的手动内存管理方式容易导致内存泄漏、悬挂指针等问题。为了解决这些问题,C++引入了智能指针。本文将详细讲解C++中智能指针的概念、种类、使用方法以及注意事项。

目录

一、引言

二、智能指针的原理及目的

2.1 智能指针的原理

2.1.1 RAII

2.2 智能指针的目的

三、智能指针的种类

3.1 std::auto_ptr

3.2 std::unique_ptr

3.3 std::shared_ptr

3.4 std::weak_ptr

四、智能指针的使用方法

五、注意事项

六、总结


一、引言

在正式讲解智能指针之前,我们先来了解一下为什么会诞生智能指针:

在C++中,指针是用于访问内存地址的一种特殊变量。传统的指针管理需要程序员手动分配和释放内存,这容易导致以下问题:

  1. 内存泄漏:当程序员忘记释放内存时,会导致内存泄漏,最终耗尽系统资源。
  2. 悬挂指针:当指针指向的内存被释放后,如果指针没有被设置为NULL,那么它就变成了悬挂指针,访问悬挂指针可能会导致未定义行为。
  3. 双重释放:当指针被错误地释放两次时,会引发程序崩溃。

为了解决这些问题,C++引入了智能指针,它是一种特殊的对象,能够自动管理指针指向的内存。

下面是一个内存泄漏的例子:

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

二、智能指针的原理及目的

了解使用智能指针之前,我们要先来了解RAII

2.1 智能指针的原理

2.1.1 RAII

RAII是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在
对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
    · 不需要显式地释放资源。
    · 采用这种方式,对象所需的资源在其生命期内始终保持有效

智能指针是一种能够自动管理指针指向内存的类模板。它通过重载解引用运算符(*)和箭头运算符(->)来模拟指针的行为,同时内部使用某种机制(RAII的原理)来自动释放内存。

总结一下智能指针的原理: 
1. RAII特性 
2. 重载operator*和opertaor->,具有像指针一样的行为。

2.2 智能指针的目的

智能指针的主要目的是:

1、自动释放内存:当智能指针超出作用域或被销毁时,它会自动释放所管理的内存。
2、防止内存泄漏和悬挂指针:智能指针确保内存被正确释放,从而避免内存泄漏和悬挂指针。

三、智能指针的种类

C++标准库提供了三种主要的智能指针:

  1. std::unique_ptr:独占智能指针,表示指针指向的内存只能由一个智能指针拥有。
  2. std::shared_ptr:共享智能指针,表示多个智能指针可以共享同一块内存。
  3. std::weak_ptr:弱指针,用于解决共享指针可能导致的循环引用问题。

在标准库出来之前,还有一个auto_ptr,下面我们会对这几个进行逐一讲解

3.1 std::auto_ptr

auto_ptr是在C++98版本中就给出的,它的实现原理是:管理权转移,只有一个对象能够管理资源

下面是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;
};

3.2 std::unique_ptr

std::unique_ptr是独占智能指针,它确保了指针指向的内存只能由一个智能指针拥有。当std::unique_ptr被销毁或赋值给另一个std::unique_ptr时,它所指向的内存会被自动释放。

下面我们来看一下库中它的声明方式:

#include <memory>

int main() {
    std::unique_ptr<int> ptr(new int(10));
    // 使用ptr
    // ...
    return 0;
}

我们来简单的模拟一下它的实现(简单粗暴,防止拷贝,这样就能确保管理权不会被转移):

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;
	}
	//将它的赋值和拷贝都禁止,就可以保证管理权无法转移
    //delete关键字:声明函数时用到这个可以使这个函数无意义
	unique_ptr(const unique_ptr<T>& sp) = delete;
	unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
private:
	T* _ptr;
};

3.3 std::shared_ptr

std::shared_ptr是共享智能指针,它允许多个智能指针共享同一块内存。std::shared_ptr内部使用引用计数来管理内存,当引用计数为0时,内存会被自动释放。

下面我们来看一下库中它的声明方式:

#include <memory>

int main() {
    std::shared_ptr<int> ptr1(new int(10));
    std::shared_ptr<int> ptr2 = ptr1;
    // 使用ptr1和ptr2
    // ...
    return 0;
}

我们来简单的模拟一下它的实现:

	//引用计数
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr=nullptr)
			:_ptr(ptr)
			,_pcount(new int(1))
		{}
		~shared_ptr()
		{
			if(--(*_pcount)==0)
			{
				cout << "~shared_ptr()" << endl;
				delete _ptr;
				delete _pcount;
			}
		}
		int use_count()
		{
			return *_pcount;
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			,_pcount(sp._pcount)
		{
			++(*_pcount);
		}
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if(_ptr!=sp._ptr)
			{
				if (--(*_pcount) == 0)
				{
					delete _ptr;
					delete _pcount;
				}
				_ptr = sp._ptr;
				(*sp._pcount)++;
				_pcount = sp._pcount;
				return *this;
			}
		}
	private:
		T* _ptr;
		int* _pcount;
	};

这里其实是涉及到线程锁的一些知识,这些内容比较靠后,等我们后面学到之后再回来讲

3.4 std::weak_ptr

std::weak_ptr是弱指针,它用于解决共享指针可能导致的循环引用问题。弱指针不会增加引用计数,因此不会阻止内存的释放。

那么什么是共享指针的循环引用呢?我们下面详细讲解一下

先看一下下面这个共享指针(就是shared_ptr)的例子

(这个样例是建立在我们上面的模拟实现上的)

//循环引用
struct ListNode
{
	int val;
	zda::shared_ptr<ListNode> prev;
	zda::shared_ptr<ListNode> next;

	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};
void test_shared_ptr3()
{
	zda::shared_ptr<ListNode> n1 = new ListNode;
	zda::shared_ptr<ListNode> n2 = new ListNode;

	//循环引用(下面这两个同时放开的时候会发生循环引用引发崩溃)
	//n1->next = n2;
	n2->prev = n1;
}

所以说shared_ptr在有些情况下会有循环引用的问题存在,比如链表,而weak_ptr就是专门来解决shared_ptr这个问题的,所以weak_ptr的模拟实现上与shared_ptr十分相似,可以直接借用

	//weak_ptr
	//不支持RAII
	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}
		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
		{}
		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			_ptr = sp.~shared_ptr;
			return *this;
		}
		//像指针一样
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};

四、智能指针的使用方法

使用智能指针时,需要注意以下几点:

  1. 初始化:智能指针必须通过new操作符或构造函数进行初始化。
  2. 赋值:智能指针之间可以相互赋值,但std::unique_ptr不能赋值给std::shared_ptr
  3. 解引用:使用解引用运算符(*)和箭头运算符(->)来访问智能指针指向的内存。
  4. 重置:使用reset方法来重置智能指针,释放当前指向的内存,并可以重新指向新的内存。
  5. 比较:智能指针之间可以使用比较运算符进行比较。

五、注意事项

  1. 避免循环引用:在使用共享智能指针时,要注意避免循环引用,否则可能导致内存无法释放。
  2. 不要使用原始指针:尽量避免使用原始指针来管理内存,使用智能指针可以简化代码并提高安全性。
  3. 了解智能指针的行为:在使用智能指针之前,要了解它们的行为,以避免潜在的问题。

六、总结

以上就是C++智能指针的知识点总结,有些涉及线程安全的问题等到后期学到之后再进行补充

感谢各位大佬观看,创作不易,还请各位大佬点赞支持!!!

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

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

相关文章

链表---数据结构-黑马

链表 定义 链表是数据元素的线性集合&#xff0c;其每个元素都指向下一个元素&#xff0c;元素存储上是不连续的。 分类 单向链表&#xff0c;每个元素只知道自己的下一个元素是谁。 双向链表&#xff0c;每个元素知道自己的上一个元素和下一个元素。 循环链表&#xff0c;…

分布式锁:Mysql实现,Redis实现,Zookeeper实现

目录 前置知识 Mysql实现分布式锁 1.get_lock函数 Java代码实现&#xff1a; 2.for update尾缀 Java代码实现&#xff1a; 3.自己定义锁表 Java代码实现&#xff1a; 4.时间戳列实现乐观锁 Java代码实现&#xff1a; Redis实现分布式锁 Zookeeper实现分布式锁&#…

Oracle搭建一主两备dataguard环境的详细步骤

​ 上一篇文章介绍了Oracle一主两备的DG环境&#xff0c;如何进行switchover切换&#xff0c;也许你会问Oracle一主两备dataguard环境要怎么搭建&#xff0c;本篇文章将为你讲述一主两备dataguard详细搭建步骤。 环境说明 主机名IP地址db_unique_name数据库角色ora11g10.10.1…

驱动数智化升级,AI大模型准备好了吗?

大数据产业创新服务媒体 ——聚焦数据 改变商业 AI大模型的快速崛起&#xff0c;为企业带来了前所未有的变革机遇。从自然语言处理到图像识别&#xff0c;从精准营销到智能制造&#xff0c;AI大模型正逐步渗透到各行各业的核心业务中。然而&#xff0c;随着技术的不断演进&…

力扣刷题-循环队列

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 思路&#xff1a; 我们在这里采用的是用数组的形式实现循环链表&#xff0c;我认为这个用数组是更为简单的&#xff0c;我们只需要控制下标就可以实现循环链表的效果。具体实现代…

Python数据可视化案例——折线图

目录 json介绍&#xff1a; Pyecharts介绍 安装pyecharts包 构建一个基础的折线图 配置全局配置项 综合案例&#xff1a; 使用工具对数据进行查看 &#xff1a; 数据处理 json介绍&#xff1a; json是一种轻量级的数据交互格式&#xff0c;采用完全独立于编程语言的文…

2024 该学前端还是学后端?

2024 该学前端还是学后端&#xff1f; 现状分析pragmatic-drag-and-drop后端开发 现状分析 对于这个问题&#xff0c;个人作为Java后端开发者&#xff0c;那么当然是比较熟悉Java后端开发&#xff0c;从这么久的工作体验来说&#xff0c;因为个人也是比较熟悉Java后端&#xf…

【第19章】Spring Cloud之Gateway自定义Logback配置

文章目录 前言一、内置配置1. 关联依赖2. 内置配置 二、自定义配置1. 日志级别2. 彩色日志3. 自定义配置4. 增加打印语句5. 效果展示 总结 前言 网关层作为我们程序的主入口&#xff0c;有着至关重要的作用&#xff0c;下面我们通过自定义Logback配置增强网关层的日志输出&…

【实用工具】Stirling-PDF入门安装教程: 优质开源的PDF处理工具/编辑工具

文章目录 项目简介功能展示Page Operations 页面操作Conversion Operations 转换操作Security & Permissions 安全与权限Other Operations 其他业务 如何安装并使用Docker RunDocker Compose 项目简介 这是一款使用 Docker 的基于本地托管网络的强大 PDF 操作工具。它能让…

2024年翻译工具新风尚:实时翻译与精准度并进

语言交流的障碍随着全球化的不断深入日益成为连接不同文化和国家的挑战。然而&#xff0c;在科技日新月异的今天&#xff0c;类似谷歌翻译这样的工具正在高速发展这。这次我们来一起探讨深受用户喜欢的翻译工具有哪些。 1.福昕在线翻译 链接直达&#xff1a;https://fanyi.pd…

贷齐乐系统最新版SQL注入(绕过WAF可union select跨表查询)

目录 标题&#xff1a;贷齐乐系统最新版SQL注入&#xff08;绕过WAF可union select跨表查询&#xff09; 内容&#xff1a; 一&#xff0c;环境部署 二&#xff0c;源码分析 三&#xff0c;sql注入 总结&#xff1a; [回到顶部]&#xff08;#article_top&#xff09; 一&am…

Linux使用学习笔记1到2 命令行与shell 基础运维命令

在学习使用ubuntu等各种喜他构建服务器的过程中遇到很多问题&#xff0c;意识到只是跟着网络的教程没办法管理好一个完整的应用部署和运行。遂开始学习linux基本知识&#xff0c;以应对服务器常见问题和软件的使用和维护。 shell 望文生义&#xff0c;大概意思是一个外壳&…

交错字符串[中等]

优质博文&#xff1a;IT-BLOG-CN 一、题目 给定三个字符串s1、s2、s3&#xff0c;请你帮忙验证s3是否是由s1 和s2交错 组成的。 两个字符串s和t交错 的定义与过程如下&#xff0c;其中每个字符串都会被分割成若干 非空 子字符串&#xff1a; s s1 s2 ... sn t t1 t2 …

数据结构---单链表实现

单链表是什么 我的理解是“特殊的数组”&#xff0c;通过访问地址来连接起来 1怎么创建链表 ----通过结构体&#xff08;成员有存入数据的data和指向下一个节点的地址的指针&#xff08;结构体指针&#xff09;next 初始架构---DataType 对应存入数据类型&#xff0c;此处的N…

一款基于Java外卖配送系统,专为多商户入驻设计,包含用户端、商家端、配送端以及总管理后台(附源码)

前言 在当前的外卖配送市场中&#xff0c;软件系统的状态常常面临一些挑战&#xff0c;例如多商户管理复杂性、用户体验不一致、后端服务的稳定性和安全性等。这些痛点不仅影响了商户和用户的满意度&#xff0c;也限制了平台的扩展性和发展潜力。 为了解决这些现状&#xff0…

B站搜索建库架构优化实践

前言 搜索是B站的重要基础功能&#xff0c;需要对包括视频、评论、图文等海量的站内优质资源建立索引&#xff0c;处理来自用户每日数亿的检索请求。离线索引数据的正确、高效产出是搜索业务的基础。我们在这里分享搜索离线架构整体的改造实践&#xff1a;从周期长&#xff0c;…

【论文阅读】BoT-SORT: Robust Associations Multi-Pedestrian Tracking

题目&#xff1a;BoT-SORT: Robust Associations Multi-Pedestrian Tracking 作者&#xff1a;Nir Aharon* Roy Orfaig Ben-Zion Bobrovsky motivation: 作者来得很直接&#xff0c;就说他们用相机运动模型和优化卡尔曼做了个可以解决具有挑战的跟踪问题的算法:BOT-SORT;说他们…

工程数学线性代数(同济大学数学系)第六版(更新中)

第1章 行列式 2 全排列和对换 一、排列及其逆序数 全排列 1个逆序、逆序数 奇排列&#xff0c;偶排列 二、对换 对换&#xff1a;排列中任意两个元素对调 相邻对换&#xff1a;相邻两个元素对换 对换改变排列的奇偶性。 4 行列式的性质 5 行列式按行&#xff08;列&…

有趣的的rce漏洞复现分析

目录 无字母数字绕过正则表达式 解读代码 解题思路 异或 或 取反 无字母数字绕过正则表达式 首先我们依然是搭建环境&#xff08;环境依然是Ubuntu下部署&#xff0c;和之前的漏洞环境一样&#xff09; <?php error_reporting(0); highlight_file(__FILE__); $code…

<数据集>车间工人、安全帽、安全背心识别<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;3465张 标注数量(xml文件个数)&#xff1a;3465 标注数量(txt文件个数)&#xff1a;3465 标注类别数&#xff1a;3 标注类别名称&#xff1a;[person, helmet, vest] 序号类别名称图片数框数1person346594732helm…