【C++入门到精通】智能指针 [ C++入门 ]

news2025/5/21 14:35:26

在这里插入图片描述

阅读导航

  • 引言
  • 一、什么是智能指针
  • 二、为什么需要智能指针
  • 三、内存泄漏
    • 1. 什么是内存泄漏,内存泄漏的危害
    • 2. 内存泄漏的示例,以及解决方法
    • 3. 内存泄漏分类
      • (1)堆内存泄漏(Heap leak)
      • (2)系统资源泄漏
    • 4. 如何检测内存泄漏
  • 四、智能指针的使用及原理
    • 1. RAII机制
      • (1)概念
      • (2)原理
      • (3)优点
    • 2. RAII机制下最基本的智能指针框架
  • 温馨提示

引言

在C++编程中,内存管理一直是一个重要的话题。手动分配和释放内存可能会导致各种问题,例如内存泄漏和悬挂指针,这些问题往往会导致程序崩溃或产生不可预测的结果。为了解决这些问题,C++提供了一种称为智能指针的机制,它可以自动管理内存分配和释放,从而避免了手动管理内存所带来的许多问题

本文将深入探讨C++中的智能指针,介绍智能指针的基本概念、类型和用法,通过深入研究C++智能指针的相关知识,我们将能够更好地理解如何编写安全、可靠的C++代码,并避免许多与手动内存管理相关的问题。无论您是初学者还是有经验的开发人员,本文都将为您提供宝贵的信息和指导,帮助您在C++编程的旅程中更上一层楼。让我们一起探索C++智能指针的精彩世界!

一、什么是智能指针

智能指针是一种可以管理动态分配的内存的 C++ 类,它能够自动释放指向的对象所占用的内存,在 C++11 中引入。这种指针可以避免程序员手动释放内存的错误和内存泄露的问题

智能指针通过封装指针,提供了一组安全而方便的操作接口来管理指针所引用的资源,例如内存。它们往往通过引用计数的方式记录对象的引用次数,并在引用次数为 0 时自动释放内存。常见的智能指针有 std::unique_ptrstd::shared_ptr,后面我们会一个一个的介绍。

使用智能指针的好处包括:动态内存的分配和回收更加安全、高效;不再需要手动进行内存管理,提高代码的可读性、可维护性;减少了出现内存泄露和野指针的概率等

二、为什么需要智能指针

下面我们先分析一下下面这段程序有没有什么内存方面的问题?代码中提出来了三个问题,进一步解释了智能指针存在的意义

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
	throw invalid_argument("除0错误");
	return a / b;
}

void Func()
{
	
	
	
	// 1、如果p1这里new 抛异常会如何?
	int* p1 = new int;
	/*如果在p1 = new int;这里使用new分配内存时抛出异常,
	由于该语句之前没有针对异常的处理机制,异常将传播到调用函数的地方(即Func()函数)。
	此时,p1指针将保持为空指针,并且由于没有正确释放内存的机会,将会导致内存泄漏。*/
	
	// 2、如果p2这里new 抛异常会如何?
	int* p2 = new int;
	/*如果在p2 = new int;这里使用new分配内存时抛出异常,
	与第一个问题类似,异常将传播到调用函数的地方(即Func()函数)。
	此时,p1指针将指向已分配的内存,而p2指针将保持为空指针。
	同样,由于没有正确释放p1指针指向的内存,将会导致内存泄漏。*/
	
	// 3、如果div调用这里又会抛异常会如何?
	cout << div() << endl;
	/*如果在div()函数的执行过程中抛出异常,该异常会立即终止当前函数的执行,
	并被传播到调用函数的地方(即Func()函数)。由于Func()函数没有处理这个异常的机制,
	它将被传播到main()函数中的异常处理部分。在main()函数的异常处理部分,
	异常对象的what()方法会被调用,将异常的信息打印到控制台。*/
	
	delete p1;
	delete p2;
}
int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

总的来说,这段代码没有充分处理可能抛出的异常情况。智能指针就可以很好的解决上面的问题,使用智能指针来管理动态内存,以避免忘记释放内存或者因为异常而导致内存泄漏的问题。

三、内存泄漏

1. 什么是内存泄漏,内存泄漏的危害

内存泄漏是指在程序运行中,由于某些原因导致已经动态分配的内存空间没有被正确释放或回收,从而造成系统内存的浪费和不足。当内存泄漏严重时,会导致系统崩溃或者变得非常缓慢

内存泄漏通常是由于程序员在使用动态内存分配函数(如 newmalloc等)时出现错误所引起的。当使用动态内存分配函数获得内存空间后,如果在使用完毕后没有及时释放,这部分内存就会成为无用内存,或者说是“死内存”,这样就会导致内存的浪费,最终导致内存耗尽。内存泄漏还可能会导致程序运行时的性能下降,甚至影响到程序的稳定性。

内存泄漏的解决方法通常是手动释放内存,或者使用智能指针等自动化工具来管理内存。此外,在编写代码时,应该避免出现内存泄漏的情况,例如尽量避免使用裸指针(raw pointer)、避免循环引用等。

2. 内存泄漏的示例,以及解决方法

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. 在第一部分,我们使用了 mallocnew 分别分配了两个整型指针 p1p2 的内存空间,但是在后续代码中没有使用 freedelete 来释放这些内存。这意味着这两个内存块将一直保留在堆上,造成内存泄漏。为了避免内存泄漏,应该在不再需要使用这些指针时,使用 freedelete 来显式释放内存。

  2. 在第二部分,我们使用了 new 来分配了一个整型数组 p3 的内存空间,但是在调用 Func() 函数之后,如果该函数抛出异常,那么 delete[] p3 将无法执行,从而导致 p3 指向的内存没有被释放。这也是一种内存泄漏情况。为了解决这个问题,可以使用异常处理机制(例如 try-catch 块),确保在发生异常时也能够正确释放内存。

修复这些内存泄漏问题的方法如下

void MemoryLeaks()
{
    // 1.内存申请了忘记释放
    int* p1 = (int*)malloc(sizeof(int));
    int* p2 = new int;
    // 在不再需要使用 p1 和 p2 时,释放内存
    free(p1);
    delete p2;

    // 2.异常安全问题
    int* p3 = new int[10];
    try {
        Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
    } catch (...) {
        // 在发生异常时,确保能够释放内存
        delete[] p3;
        throw; // 继续抛出异常
    }
    delete[] p3;
}

这样,通过显式地释放内存,并在发生异常时进行异常处理,可以避免上述代码中的内存泄漏问题。

3. 内存泄漏分类

🚩C/C++程序中一般我们关心两种方面的内存泄漏:堆内存泄漏(Heap leak)和系统资源泄漏

(1)堆内存泄漏(Heap leak)

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

(2)系统资源泄漏

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

4. 如何检测内存泄漏

✅检测内存泄漏可以使用一些工具和技术。下面是几种常用的方法:

  1. 静态代码分析:使用静态分析工具(例如Clang Static Analyzer,Cppcheck等)对代码进行扫描,以检测潜在的内存泄漏问题。这些工具可以在编译期间检查代码,并提供警告或错误提示。

  2. 重载内存分配函数:通过重载 newdelete 运算符,可以跟踪内存的分配和释放情况。你可以重载全局版本的 newdelete,或者在需要跟踪的类中重载它们。通过在这些重载函数中添加自定义的日志记录或计数机制,可以检测内存泄漏。

  3. 自定义内存管理器:实现自己的内存管理器可以更好地控制内存的分配和释放过程,并能够记录和追踪分配的内存。通过在分配和释放内存时维护一个内存块的列表,可以检测内存泄漏情况。

  4. 内存泄漏检测工具:某些集成开发环境(IDE)和调试器提供内置的内存泄漏检测功能。—>>🔴内存泄露检测工具

四、智能指针的使用及原理

1. RAII机制

(1)概念

RAII(Resource Acquisition Is Initialization)是一种C++编程技术,它通过将资源的获取和释放与对象的生命周期绑定在一起,以确保资源在对象创建时获取,在对象销毁时释放。这种技术利用了C++对象的构造函数和析构函数的调用机制,使得资源的管理变得更加简洁、安全和可靠。

(2)原理

使用RAII的关键在于将资源的获取和释放操作分别放置在对象的构造函数和析构函数中。当对象被创建时,构造函数负责获取资源并进行必要的初始化工作;当对象被销毁时,析构函数负责释放资源并进行清理工作。由于C++保证在对象销毁时析构函数会被自动调用,所以资源的释放也就得到了保证。

(3)优点

RAII技术的优点如下:

  1. 简洁性:通过对象的自动构造和析构,减少了手动管理资源的代码量,使得程序更加简洁易读。
  2. 安全性:确保资源在适当的时候被释放,避免了常见的资源泄漏和错误状态的产生。
  3. 可靠性:无论在何时何地发生异常或提前退出,都能够保证资源的正确释放,提高程序的可靠性。
  4. 可扩展性:通过继承和组合等方式,可以方便地扩展和管理更复杂的资源。

常见的使用RAII技术的例子包括使用智能指针管理动态内存、使用文件对象进行文件操作、使用互斥锁类进行线程同步等。通过合理运用RAII技术,可以有效地提高代码的可维护性、可读性和可靠性,是现代C++编程中的重要技术之一。

2. RAII机制下最基本的智能指针框架

#include <iostream>
#include <memory>
using namespace std;

// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
public:
    SmartPtr(T* ptr = nullptr)
        : _ptr(ptr)
    {}

    ~SmartPtr()
    {
        if(_ptr)
            delete _ptr;
    }

private:
    T* _ptr;
};

// 除法函数,可能抛出异常
int div()
{
    int a, b;
    cin >> a >> b;
    if (b == 0)
        throw invalid_argument("除0错误");
    return a / b;
}

void Func()
{
    SmartPtr<int> sp1(new int); // 使用SmartPtr管理动态分配的int对象
    SmartPtr<int> sp2(new int); // 使用SmartPtr管理动态分配的int对象

    cout << div() << endl; // 调用div函数进行除法运算

    // 在Func函数结束时,SmartPtr析构函数会自动释放资源
}

int main()
{
    try {
        Func(); // 调用Func函数
    }
    catch(const exception& e)
    {
        cout << e.what() << endl; // 捕获并输出异常信息
    }

    return 0;
}

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

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

重载operator*opertaor->,具有像指针一样的行为,这样才能像指针一样使用。

温馨提示

感谢您对博主文章的关注与支持!另外,我计划在未来的更新中持续探讨与本文相关的内容,会为您带来更多关于C++以及编程技术问题的深入解析、应用案例和趣味玩法等。请继续关注博主的更新,不要错过任何精彩内容!

再次感谢您的支持和关注。期待与您建立更紧密的互动,共同探索C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!
在这里插入图片描述

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

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

相关文章

Nginx配置动静分离实例(Nginx处理静态资源)

Nginx动静分离概述 Nginx 动静分离是指动态请求跟静态请求分开&#xff0c;可以理解为使用Nginx处理静态页面&#xff08;包含静态资源文件&#xff09;&#xff0c;Tomcat处理动态页面&#xff1b; 提醒一下&#xff1a;下面实例讲解是在Mac系统演示的&#xff1b; 两个方式…

恢复 iPhone 和 iPad 数据的 10 个好工具 - [苹果数据恢复]

它发生了.. 有时您需要从您的手机或平板设备恢复重要数据。 许多人已经开始将重要文件存储在手机上&#xff0c;因为他们几乎可以在任何情况下随时随地轻松访问数据。 不言而喻; 您可以轻松访问您的电子邮件、共享图片、编辑和共享文档、支付账单等等&#xff0c;只需在您的手…

【k8s】Kubernetes 声明式 API、命令式

1. 资源管理方式&#xff1a; 1>. 命令式对象管理∶直接使用命令去操作kubernetes资源 kubectl run nginx-pod --imagenginx:1.17.1 --port802>. 命令式对象配置∶通过命令配置和配置文件去操作kubernetes资源 kubectl create/patch -f nginx-pod.yaml3>. 声明式对…

win10重新安装Windows应用商店

安装Windows 终端 用 PowerShell 重装 Microsoft Store使用 MSIX 包重装 Microsoft Store下载安装包及依赖下载Windows 应用商店的安装包安装包是依赖组件包 微软应用商店无法连接网络解决办法 参考&#xff1a; wind10自带的终端程序和powerShell 真是太垃圾了&#xff0c; 突…

简单高效LaTeX 科学排版 第005集 导言区和文档输出

导言区是LaTeX文档的前导重要部分&#xff0c;这个视频讨论了建立导言区的方法&#xff0c;并且讨论了LaTeX文档的输出。 视频链接&#xff1a;https://www.ixigua.com/7298100920137548288?id7303715340075139622&logTag6eb24f453fe9fe617a61

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《与新能源互补和独立参加多级市场的抽蓄电站容量分配策略》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 这个标题涉及到抽蓄电站在能源系统中的角色&#xff0c;特别是在多级市场中的参与&#xff0c;并强调了新能源的互补性以及抽蓄电站的独立性。下面我将…

领域驱动设计应用之WebAPI

领域驱动设计应用之WebAPI 此篇文章主要讲述领域驱动设计在WebApi中的应用&#xff0c;以及设计方式&#xff0c;这种设计的原理以及有点。 文章目录 领域驱动设计应用之WebAPI前言一、相对于传统设计模式的有点二、WebAPI对接中的使用案例业务拆分父类设计HttpResponse(返回)…

计算机三级(网络技术)一综合题(IP地址计算)

例题一 &#xff08;正常算&#xff09; 计算并填写下表 地址类别 A类地址段是1.0.0.0~127.255.255.255 1~127 B类地址段是128.0.0.0~191.255.255.255 128~191 C类地址段是192.0.0.0~223.255.255.255 192~223 所以41填A 网络地址为主机位全0 根据子网掩码&…

计算机网络安全教程(第三版)课后简答题答案大全[6-12章]

目录 第 6 章 网络后门与网络隐身 第 7 章 恶意代码分析与防治 第 8 章 操作系统安全基础 第 9 章 密码学与信息加密 第 10 章 防火墙与入侵检测 第 11 章 IP安全与Web安全 第 12 章 网络安全方案设计 链接&#xff1a;计算机网络安全教程(第三版)课后简答题答案大全[1-5…

Spark---RDD序列化

文章目录 1 什么是序列化2.RDD中的闭包检查3.Kryo 序列化框架 1 什么是序列化 序列化是指 将对象的状态信息转换为可以存储或传输的形式的过程。 在序列化期间&#xff0c;对象将其当前状态写入到临时或持久性存储区。以后&#xff0c;可以通过从存储区中读取或反序列化对象的…

web前端算法简介之链表

链表 链表 VS 数组链表类型链表基本操作 创建链表&#xff1a;插入操作&#xff1a;删除操作&#xff1a;查找操作&#xff1a;显示/打印链表&#xff1a;反转链表&#xff1a;合并两个有序链表&#xff1a;链表基本操作示例 JavaScript中&#xff0c;instanceof环形链表 判断…

重学Java 4 进制转换和位运算

天赋不好好使用的话&#xff0c;可是会被收回的哦 ——24.1.13 一、进制转换 1.常用的进制 2.十进制和二进制之间的转换 1.十进制转二进制 辗转相除法——循环除以2&#xff0c;取余数&#xff0c;除到商为0为止&#xff0c;除完后&#xff0c;由下往上&#xff0c;得出换算后…

设计模式-- 3.适配器模式

适配器模式 将一个类的接口转换成客户希望的另外一个接口。使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 角色和职责 请求者&#xff08;client&#xff09;&#xff1a;客户端角色,需要使用适配器的对象&#xff0c;不需要关心适配器内部的实现&#xff0c;…

爬虫—中信证券资管产品抓取

爬虫—中信证券资管产品抓取 中信证券资管产品板块网址&#xff1a;http://www.cs.ecitic.com/newsite/cpzx/jrcpxxgs/zgcp/ 页面截图如下&#xff1a; 目标&#xff1a;抓取上图中红框内的所有资产信息 按F12进入开发者工具模式&#xff0c;在Elements板块下&#xff0c;在…

学习redis有效期和数据类型

1、安装redis和连接redis 参考&#xff1a;ubuntu安装单个redis服务_ubuntu redis单机版安装-CSDN博客 连接redis&#xff1a;redis-cli.exe -h localhost -p 6379 -a 123456 2、Redis数据类型 以下操作我们在图形化界面演示。 2.1、五种常用数据类型介绍 Redis存储的是key…

大创项目推荐 深度学习疲劳检测 驾驶行为检测 - python opencv cnn

文章目录 0 前言1 课题背景2 相关技术2.1 Dlib人脸识别库2.2 疲劳检测算法2.3 YOLOV5算法 3 效果展示3.1 眨眼3.2 打哈欠3.3 使用手机检测3.4 抽烟检测3.5 喝水检测 4 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习加…

Sonar Qube基本使用

中文化 Sonar Qube的使用方式很多&#xff0c;Maven可以整合&#xff0c;也可以采用sonar-scanner的方式&#xff0c;再查看Sonar Qube的检测效果 Sonar-scanner实现代码检测 下载Sonar-scanner&#xff1a;https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/…

线性代数——(期末突击)概率统计习题(概率的性质、全概率公式)

目录 概率的性质 题一 全概率公式 题二 题三 概率的性质 有限可加性&#xff1a; 若有限个事件互不相容&#xff0c;则 单调性&#xff1a; 互补性&#xff1a; 加法公式&#xff1a; 可分性&#xff1a; 题一 在某城市中共发行三种报纸&#xff1a;甲、乙、丙。在这个…

[Vue]从数据库中动态加载阿里巴巴矢量图标的两种方式

记录一次在Vue中动态使用阿里巴巴矢量图标库 这是本人第一次使用阿里巴巴的矢量图标库&#xff0c;简单的导入和使用的话网上的教程很多&#xff0c;这里不多赘述&#xff0c;本人的需求是从数据库中加载出来并且显示到页面上&#xff0c;接下来简述一下如何实现。 以下代码均是…

解锁思维潜能,畅享XMind 2024 Mac/win中文版思维导图软件

XMind 2024是一款功能强大的思维导图软件&#xff0c;旨在帮助用户提高工作效率和组织思维。它的核心特点包括多平台同步、强大的协作功能和丰富的导图模板。 首先&#xff0c;XMind 2024支持多平台的无缝同步&#xff0c;用户可以在电脑、手机和平板上随时随地访问和编辑自己…