C++智能指针-保姆级讲解带你一文搞懂智能指针(附核心代码实现+讲解)

news2024/11/15 13:35:10

0

C++智能指针

  • 1.引言
    • 1.1 为什么会出现智能指针
    • 1.2内存泄漏
      • 1.2.1 什么是内存泄漏,内存泄漏的危害
      • 1.2.2 内存泄漏分类
      • 1.2.3如何检测内存泄漏
      • 1.2.4如何避免内存泄漏
  • 2. 智能指针的使用及原理
  • 3.常见智能指针
    • 3.1std::auto_ptr
    • 3.2std::unique_ptr
    • 3.3std::share_ptr

1.引言

1.1 为什么会出现智能指针

C++中引入智能指针的主要目的是为了解决动态内存管理的问题(C++不像Java等语言有垃圾回收机制 C+对性能要求极高,所以没有垃圾碎片回收机制,对于内存的释放要特别小心)。传统的指针(裸指针)在使用时需要手动分配和释放内存,容易出现内存泄漏和悬挂指针等问题。智能指针通过封装裸指针,并提供自动内存管理功能,使得内存资源可以更安全、高效地管理。
智能指针具有以下优点:

  1. 自动内存管理:智能指针使用了RAII(资源获取即初始化)的原则,即在构造函数中获取资源,在析构函数中释放资源。这样可以确保资源的正确释放,避免内存泄漏。

  2. 所有权管理:智能指针可以跟踪动态分配的内存资源的所有权,确保只有一个智能指针拥有该资源。当所有智能指针超出作用域或被重新赋值时,会自动释放内存,避免出现悬挂指针和内存访问错误。

  3. 共享所有权:某些智能指针(如std::shared_ptr)允许多个指针共享同一块内存资源,当所有共享指针超出作用域时,才会释放内存。这样可以有效地管理资源的共享和释放,避免出现资源被提前释放或多次释放的问题。

  4. 防止空悬指针:智能指针在初始化时会进行空指针检查,避免了访问空指针导致的程序崩溃或未定义行为。

  5. 提高代码可读性和维护性:使用智能指针可以简化内存管理的代码逻辑,减少手动内存释放的疏忽和错误。同时,智能指针的语义清晰,可以更好地表达程序的意图,提高代码的可读性和维护性。

总之,智能指针是C++中一种重要的工具,它提供了更安全、高效的内存管理方式,帮助程序员避免了很多与内存管理相关的问题。通过使用智能指针,可以简化代码、提高可靠性,并减少内存泄漏等问题的发生。

1.2内存泄漏

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

1.2.2 内存泄漏分类

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

1.2.3如何检测内存泄漏

  • 在linux下内存泄漏检测: linux下几款内存泄漏检测工具链接
  • 在windows下使用第三方工具: VLD工具说明链接
  • 其他工具: 内存泄漏工具比较链接

1.2.4如何避免内存泄漏

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

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

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

  • 一个简单的智能指针
#pragma once
#include<iostream>
namespace GXPYY
{
	template<class T>
	class smartptr
	{
	public:
		smartptr(T* ptr = nullptr)
			:_ptr(ptr)
		{};
		~smartptr()
		{
			cout << "ptr delet..." << endl;
			if (_ptr)
				delete _ptr;

		}

	private:
		T* _ptr;
	};

}
  • 测试+运行
    pic2
    可见 当指针sp1234生命周期结束时,智能指针就会把相关的资源释放,避免内存泄漏,下面将赋值操作完善一下。
#pragma once
#include<iostream>
namespace GXPYY
{
	template<class T>
	class smartptr
	{
	public:
		smartptr(T* ptr = nullptr)
			:_ptr(ptr)
		{};

		~smartptr()
		{
			cout << "ptr delet..." << endl;
			if (_ptr)
				delete _ptr;

		}

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

		//针对map类型对象可以进行-》访问
		T* operator->()
		{
			return _ptr;
		}


	private:
		T* _ptr;
	};

}


  • 结果:
    pic4
    这样一个简单的智能指针完成啦
    思考一个问题:拷贝怎么实现?拷贝问题是智能指针的难点!!!!

3.常见智能指针

3.1std::auto_ptr

std::auto_ptr是C++98标准中提供的智能指针,它是独占式智能指针,用于管理动态分配的对象。它通过在对象析构时自动释放内存来解决资源管理的问题。

  • 核心代码逻辑
	//拷贝构造
		//auto_ptr
		smartptr(smartptr <T>& ptr)
			:_ptr (ptr._ptr)
		{
			ptr._ptr = nullptr;
		}

pic6
通过调试窗口可以发现,再完成拷贝之后,sp3被重置为0x000了,也就是悬空指针
pic7
再对sp3进行操作时候就会出现异常!悬空指针不能再进行操作。
所以可以总结一下:auto_ptr的核心思想就是–控制权转移
由于它弊端比较拉跨,我们一般不会使用它作为智能指针!
它是c+98的产物,可以理解为是c+发展过程中的实验品。

3.2std::unique_ptr

std::unique_ptr是C++11标准引入的智能指针,其核心思想是独占所有权(exclusive ownership)和资源管理的责任。

核心思想可以总结为以下几点:

  • 独占所有权:std::unique_ptr独占所管理的指针资源,同一时间只能有一个std::unique_ptr拥有该资源。当std::unique_ptr被销毁或转移所有权时,它会自动释放所管理的资源,确保资源在适当的时候被正确释放,避免资源泄漏。

  • 确保资源的释放:std::unique_ptr通过在析构函数中自动调用delete来释放所管理的资源。这意味着,无论是通过正常的控制流还是异常的控制流,只要std::unique_ptr被销毁,资源都会得到释放,避免了手动释放资源的繁琐和可能的遗漏。

  • 禁止拷贝:std::unique_ptr禁止拷贝构造函数和拷贝赋值运算符的使用,以确保同一时间只有一个std::unique_ptr拥有资源的所有权。这样可以防止多个智能指针同时管理同一块资源,避免了资源的重复释放和悬挂指针的问题。

  • 支持移动语义:std::unique_ptr支持移动构造函数和移动赋值运算符,允许资源的所有权从一个std::unique_ptr对象转移到另一个对象,避免了资源的不必要拷贝。

总的来说,std::unique_ptr的核心思想是通过独占所有权和自动释放资源的方式,提供了一种安全、高效的资源管理机制,减少了手动管理资源的复杂性和错误,并支持现代C++中的移动语义。

可以看到,**unique_ptr对于拷贝构造的处理机制就是----->禁止拷贝!**简单粗暴

  • 核心代码逻辑:
public:
		// RAII思想
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}

		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "delete" << _ptr << endl;
				delete _ptr;
				_ptr = nullptr;
			}
		}

		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}

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

		T* get()
		{
			return _ptr;
		}

不通过拷贝构造实现!

3.3std::share_ptr

std::shared_ptr是C++11标准引入的智能指针,其核心思想是共享所有权(shared ownership)和引用计数。

核心思想可以总结为以下几点:

  1. 共享所有权:std::shared_ptr可以与其他std::shared_ptr共享对同一块资源的所有权。多个std::shared_ptr可以指向同一个对象,它们共同管理对象的生命周期。当所有std::shared_ptr都离开作用域或被显式销毁时,资源才会被释放。

  2. 引用计数:std::shared_ptr内部维护了一个引用计数器,用于跟踪有多少个std::shared_ptr对象共享对资源的所有权。每当创建一个std::shared_ptr对象时,引用计数会加一;当std::shared_ptr对象被销毁时,引用计数会减一。只有当引用计数为零时,资源才会被释放。

  3. 自动内存管理:std::shared_ptr通过在析构函数中检查引用计数,当引用计数为零时,自动释放所管理的资源。这样,无需手动释放资源,避免了资源泄漏。

  4. 拷贝和赋值:std::shared_ptr支持拷贝构造函数和拷贝赋值运算符,允许多个std::shared_ptr对象共享对同一资源的所有权。拷贝一std::shared_ptr会增加引用计数,销毁一个std::shared_ptr会减少引用计数。

  5. 循环引用的解决:std::shared_ptr通过使用弱引用(std::weak_ptr)来解决循环引用的问题。弱引用不会增加引用计数,可以监视std::shared_ptr对象的生命周期,避免循环引用导致的资源泄漏。

对于拷贝构造 share_ptr引用了一个计数器

  • 核心代码逻辑
template<class T>
	class shared_ptr
	{
	public:
		void Release()
		{
			//析构一次 pcount--
			//当计数器==0时且ptr存在才释放资源
			if (--(*_pCount) == 0 && _ptr)
			{
				cout << "delete" << _ptr << endl;
				delete _ptr;
				_ptr = nullptr;

				delete _pCount;
				_pCount = nullptr;
			}
		}

		// RAII思想
		shared_ptr(T* ptr )
			:_ptr(ptr)
			, _pCount(new int(1))//构造的时候把pcount赋值1
		{}

		~shared_ptr()
		{
			Release();
		}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pCount(sp._pCount)
		{
			(*_pCount)++;
		}



		// sp1 = sp3
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//if (this != &sp)
			if (_ptr != sp._ptr)
			{
				Release();

				_ptr = sp._ptr;
				_pCount = sp._pCount;
				++(*_pCount);
			}

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

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

		T* get()
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pCount;
	};
  • 调试
    pic8
    可见 拷贝之后,sp4 和sp3指向同一块内存,而且pcount计数器++为2了,那么我们试着去析构一下看看怎么样。
    pic8
    可见 析构之后并不会去直接释放资源,而是对相应的pcount–;

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

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

相关文章

【雕爷学编程】Arduino动手做(116)---五向导航按键模块

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

Axure教程—环形进度条

本文将教大家如何用AXURE制作环形进度条 一、效果 预览地址&#xff1a;https://mmfwgo.axshare.com 二、功能 &#xff08;1&#xff09;、点击“开始”按钮&#xff0c;环形进度开始执行&#xff0c;“开始”按钮转换为“暂停”按钮 &#xff08;2&#xff09;点击“重置”按…

delphi的ARM架构支持与System.Win.WinRT库

delphi的ARM架构支持与System.Win.WinRT库 目录 delphi的ARM架构支持与System.Win.WinRT库 一、WinRT 二、delphi的System.Win.WinRT库 2.1、支持ARM芯片指令 2.2、基于WinRT技术的特点 2.3、所以使用默认库而未经转化的服务端应用并不支持ARM架构服务器 2.4、对默认库…

自学网安遇到问题了该怎么解决

自学网络安全很容易学着学着就迷茫了&#xff0c;找到源头问题&#xff0c;解决它就可以了&#xff0c;所以首先咱们聊聊&#xff0c;学习网络安全方向通常会有哪些问题&#xff0c;看到后面有惊喜哦 1、打基础时间太长 学基础花费很长时间&#xff0c;光语言都有几门&#xf…

计算机程序设计的艺术--第一卷--第一章(1)

1 BASIC CONCEPT 1.1 algorithm algorithm 是所有计算机编程的本质&#xff0c;我们要仔仔细细的追溯一下这个概念是怎么来的。 algorithm 这个词&#xff0c;非常有意思&#xff0c;乍一看&#xff0c;好像是要写 logarithm&#xff0c;写错了&#xff0c;写成了 algorithm…

QTYX量化系统实战案例分享|均线多头排列惯性突破前高-202306第五弹

前言 “实战案例分享系列”是和大家分享一些股票量化分析工具QTYX在实战中的应用案例&#xff08;包括失败的案例&#xff09;&#xff0c;这样能够帮助大家更好地去理解QTYX中的功能设计&#xff0c;也能更好地帮助大家搭建出属于自己的量化交易系统。 关于QTYX的使用攻略可以…

Java Spark 操作 Apache Kudu

一、Apache Kudu Apache Kudu是一种列式分布式存储引擎&#xff0c;它的设计目标是支持快速分析和高吞吐量的数据访问&#xff0c;同时也能够支持低延迟、实时查询和更新操作。它被称为Hadoop生态系统的新一代存储层&#xff0c;能够与Apache Spark、Apache Impala、Apache Hiv…

Python3 字典与集合 | 菜鸟教程(七)

目录 一、Python3 字典 &#xff08;一&#xff09;字典是另一种可变容器模型&#xff0c;且可存储任意类型对象。 &#xff08;二&#xff09;字典的每个键值 key>value 对用冒号 : 分割&#xff0c;每个对之间用逗号(,)分割&#xff0c;整个字典包括在花括号 {} 中 &am…

西南交通大学智能监测 培训课程练习5

2023.06.17培训 linux的简单实用 打包、部署后端jar服务 目录 一、连接远程服务器 二、maven项目打包 2.1添加build依赖 2.2使用maven打包 三、Linux基础操作 3.1利用Xftp上传文件 3.1.1返回上一层目录 3.1.2查看文件 3.1.3进入文件 3.1.4创建文件夹 3.1.5上传文件 …

安装Apache mysql php

一.Apache网站服务 Apache起源 源于 APatchy Server&#xff0c;著名的开源Web服务软件 1995年时&#xff0c;发布Apache服务程序的1.0版本 由Apache软件基金会 (ASF) 负责维护 最新的名称为“Apache HTTP Server 安装Apache----下面两个插件是httpd2.4以后的版…

SpringBoot整合activiti7实现简单的员工请假流程

Activiti 是一个开源架构的工作流引擎&#xff0c;基于bpmn2.0 标准进行流程定义。其前身是JBPM&#xff0c;Activiti 通过嵌入到业务系统开发中进行使用。 整合springboot 引入相关依赖 <!-- 引入Activiti7 --><dependency><groupId>org.activiti</gro…

什么是2.5G和5G多千兆端口?

概要 在当前数字化时代&#xff0c;对于高速数据传输和网络连接的需求不断增长。为了满足这种需求&#xff0c;网络技术也在不断发展和进步。2.5G和5G多千兆端口是一种新型的网络连接技术&#xff0c;提供了比传统千兆以太网更高的传输速率和带宽。本文将详细介绍 的定义、工作…

[元带你学: eMMC协议详解 15] 写保护(Write Protect)详解

依JEDEC eMMC 5.1及经验辛苦整理&#xff0c;付费内容&#xff0c;禁止转载。 所在专栏 《元带你学: eMMC协议详解》 内容摘要 全文 1300字&#xff0c; 主要讲述写保护的用法&#xff0c; 写保护的类型。 Write Protect Management 为了允许主机保护数据不被擦除或覆盖写入&…

vue-server-renderer实现服务端渲染

vue-server-renderer实现服务端渲染 简单认识vue-server-renderer&#xff1a; 是 Vue.js 官方提供的一个库&#xff0c;用于将 Vue 组件渲染成 HTML 字符串或流&#xff0c;通常用于服务端渲染。 具体的咱们vue-server-renderer如何实现 1、预编译组件&#xff1a;根据 Vue …

云安全技术(一)之什么是云计算

对于在云环境中工作的安全专家而言&#xff0c;从传统数据中心模型获得的许多知识和最佳实践仍然适用于云计算环境&#xff0c;但安全专家对云计算概念、不同类型的云模型和云服务的深入理解对于成功实施和监督(Overseeing)安全策略和合规性至关重要。 什么是云计算 1.1 云计…

扫码枪(扫描枪)扫码在vue中的使用教学

1.扫描枪使用原理浅析。 扫描枪的使用原理其实很简单&#xff1a;就是把光信号转换成电信号&#xff0c;再将电信号通过模拟数字转换器转化为数字信号传输到计算机中处理。其实可以简单理解为&#xff1a;二维码/条形码 转换成 字符串。 2.扫描枪功能开发前准备。 正所谓“工…

关于【C语言】中scanf与getchar的用法和常见错误详解

写这篇博客的起因是最近博主自己学习中总是遇到类似的错误&#xff0c;并曾百思不得其解。 今天分享出来是希望帮助大家在写代码时避免这些错误。话不多说&#xff0c;我们直接开始吧&#xff01; 君兮_的个人主页 勤时当勉励 岁月不待人 C/C 游戏开发 输入函数scanf与getcha…

[架构之路-213]- 架构 - 架构设计过程快速概览与在线画图工具

目录 第一步&#xff1a;业务系统 &#xff08;1&#xff09;收集目标系统的用户需求 &#xff08;2&#xff09;定义用例图 第二步 领域建模 &#xff08;1&#xff09;业务流程定义 &#xff08;2&#xff09;业务功能分解 &#xff08;3&#xff09;非功能性架构&…

贝莱德出手了!

* * * 原创&#xff1a;刘教链 * * * 号外&#xff1a;今天在“刘教链Pro”发表了头条《内参&#xff1a;贝莱德向SEC申请的究竟是BTC信托还是现货ETF&#xff1f;信托和ETF的5点关键区别》&#xff0c;以及次条《私钥争夺战》&#xff0c;欢迎关注公众号“刘教链Pro”并阅读。…

mysql压缩包方式安装、data数据恢复

前言 最近电脑重装了系统&#xff0c;C盘彻底格式化了&#xff0c;但是D盘中的文件还是保留了下来。 我的MySQL的数据都在D盘了&#xff0c;想要重新恢复MySQL&#xff0c;还是很简单的&#xff1a; 重新安装Mysql将源数据拷贝到新的Mysql的data目录下 顺便记录一下MySQL压缩…