【C++ 】智能指针:内存管理的 “自动导航仪”

【C++ 】智能指针:内存管理的 “自动导航仪”

目录

一、引入

二、智能指针的两大特性:

1、RAII

特点:

好处:

2、行为像指针

三、智能指针起初的缺陷:拷贝问题

四、几种智能指针的介绍。

1、C++98出现的智能指针——auto_ptr

auto_ptr解决上述拷贝构造的问题:

2、boost库

3、unique_ptr

4、shared_ptr

引用计数的实现:

赋值运算符的问题:(循环引用)

5、weak_ptr

特点:

解决循环引用问题:

五、C++智能指针的基本框架:

六、定制删除器,以及包装器的使用场景之一

七、内存泄漏:

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

2、内存泄漏的分类

八、关于C++智能指针的相关代码:

std::unique_ptr

std::shared_ptr

std::weak_ptr


前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家

点击跳转到网站

一、引入

首先通过一个使用场景来引入智能指针,如下:

这里有一个类HF,一个子函数fun,在fun里面new了三个HF对象,然后delete,正常情况下delete会先调用析构函数,然后再释放相应的资源:

二、智能指针的两大特性:

智能指针的两大特性:

1、RAII

2、行为像指针

1、RAII

  是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。(通俗来讲,就是将资源交给一个类对象来管理,通过该类的构造函数交给对象。)

特点:

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。

好处:

(1)、不需要显式地释放资源,而是通过智能指针间接帮忙释放(2)、采用这种方式,对象所需的资源在其生命期内始终保持有效

2、行为像指针

智能指针实际也是一个类,要是行为像一个指针,即要重载解引用(*),箭头(->),甚至有时还要重载方括号([ ])。

三、智能指针起初的缺陷:拷贝问题

首先我们实现一个简易版智能指针:

new了一个日期类对象交给智能指针管理,智能指针对象存在期间,资源都是存在的,最后智能指针对象生命周期结束,调用析构函数释放,同时释放掉资源(delete);

但是当我们用对象sp1去拷贝构造sp2时:



此时就会报错:



原因在于我们没有实现拷贝构造,此时默认拷贝构造就是浅拷贝,这样两个对象的成员变量都会指向这份资源,最后调用析构函数时,就会对这份资源delete两次,从而造成野指针的问题。如何解决这个问题,在第四点进行介绍。

四、几种智能指针的介绍。

1、C++98出现的智能指针——auto_ptr

头文件:memory

具体信息可以查看官网文档。

auto_ptr解决上述拷贝构造的问题:

auto_ptr是直接将资源的管理权转移,用对象sp1去拷贝构造sp2,那么就会将sp1的资源的管理权交给sp2管理,而sp1被置空。



大致处理方法如下:拷贝后将sp1置空就行了:



注意,C++11的移动语义也是资源的转移,但和这里是不一样的,移动语义是针对将亡值去转移资源,而这里sp1不是将亡值。

这样做是有些问题的,这里资源转移后,sp1就悬空了,此时拷贝后就不能去访问sp1,否则就会出现空指针的问题,所以很多公司都禁止使用auto_ptr;

2、boost库

提到智能指针,就得提一下boost库,boost库是C++第三方库,里面就有智能指针,而C++的智能指针就是从这个库里面引入的,然后进行了略微修改。

3、unique_ptr

该智能指针解决拷贝构造的问题的方法就是:简单粗暴,禁止拷贝,适用于不需要拷贝的场景。



底层实际就是将拷贝构造给delete了:



同时,赋值运算符重载也要禁掉,默认生成的赋值运算符重载也是浅拷贝。

4、shared_ptr

当遇到需要拷贝构造的场景时,就需要使用shared_ptr,shared_ptr解决拷贝构造的问题的方法是:引用计数,去解决多次析构的问题。

引用计数的实现:

引用计数:记录当前有几个对象参与管理这个资源,在某个对象析构时,就将引用计数减1,当最后一个对象析构时才去释放资源。



要实现引用计数,就需要一份资源对应一个计数,有人会想到定义一个静态成员count,但实则不行,因为静态成员是属于整个类的,属于所有对象。管理一个资源的时候是可以解决的,但当第二个资源出现时,就不能适用了,因为不同资源之间的引用计数都是同一个静态成员变量,所以会相互影响。
实际上的实现如下:



增加一个成员变量*pcount,即指向引用计数的指针,在构造的时候(即资源来了),就new一个计数给该指针,在拷贝构造发生的时候,除了使两个对象指向同一个资源外,两个对象的引用计数也要指向同一个,并且要记得把引用计数++一下,在某个对象析构时,就将引用计数减1,然后判断是否为最后一个对象的析构,如果是的话就释放资源。

赋值运算符的问题:(循环引用)

shared_ptr虽然解决了拷贝构造的问题,但正因为引用计数的这样实现,又会造成赋值运算符重载后出现问题。

赋值运算符简单重载:



为了分析这里的缺陷,我们引入一个场景:双向链表的赋值:

这是双向链表的简单实现



因为会将链表资源交给智能指针管理,如下:



所以链表的定义中,成员next和prev的类型也应该是智能指针,不然在赋值的时候会出现类型不同的问题,正因为需要这样设计,问题就来了。

一般情况上述实现是没有问题的,但当执行下面两句代码后,问题就来了:



这是在链接两个节点,链接完后就会这样:



首先出现两个节点分别由n1和n2指向,此时两个节点的引用计数分别都是1,当执行n1->next = n2时,n2指向的节点的引用计数就会变成2;当执行n2->prve = n1时,n1指向的节点的引用计数就会变成2。

最后当析构链表时:



这样就形成了一个闭环,导致这两个节点的内存泄漏,这个问题也叫循环引用。当两个shared_ptr互相引用就会出现循环引用的问题。

5、weak_ptr

为了解决shared_ptr的循环引用问题,所以引入了weak_ptr。

特点:

weak_ptr的特点:没有引用计数,支持默认构造,构造函数的形参没有指针,因为该智能指针不参与资源管理,但自身成员变量会有一个指针,但会被置空,weak_ptr的重点在于拷贝构造和赋值。

解决循环引用问题:

这里的不同是将链表的成员变量_next和_prev的类型变为weak_ptr,正因为weak_ptr没有增加引用计数,所以在节点链接的时候,引用计数不会增加,所以节点会正常释放。



五、C++智能指针的基本框架:

六、定制删除器,以及包装器的使用场景之一

七、内存泄漏:

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

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

2、内存泄漏的分类

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

八、关于C++智能指针的相关代码:

此小点内容来源于:豆包AI

在 C++ 里,手动管理动态分配的内存容易引发内存泄漏、悬空指针等问题。智能指针作为一种类模板,能有效管理动态分配的内存,避免这些问题的出现。C++ 标准库提供了三种主要的智能指针:std::unique_ptrstd::shared_ptr 和 std::weak_ptrstd::unique_ptr

std::unique_ptr 属于独占式智能指针,它对所指向的对象拥有唯一的所有权。一旦 std::unique_ptr 被销毁,其指向的对象也会随之被自动销毁。 

在 uniquePtrExample 函数中: 借助 std::make_unique 创建了一个 std::unique_ptr,它指向 MyClass 的一个对象。调用 doSomething 方法来使用这个对象。尝试复制 std::unique_ptr 会引发编译错误,因为它不允许复制,不过可以使用 std::move 转移所有权。转移所有权之后,原 std::unique_ptr 变为空。std::shared_ptr

std::shared_ptr 是共享式智能指针,多个 std::shared_ptr 能够指向同一个对象。它采用引用计数来管理对象的生命周期,当引用计数变为 0 时,对象就会被销毁。 

在 sharedPtrExample 函数中: 利用 std::make_shared 创建了一个 std::shared_ptr,它指向 MyClass 的一个对象。通过 use_count 方法可以查看当前的引用计数。复制 std::shared_ptr 会使引用计数增加。调用 reset 方法可以释放 std::shared_ptr,从而使引用计数减少。std::weak_ptr

std::weak_ptr 是弱引用智能指针,它不拥有对象的所有权,只是对 std::shared_ptr 所管理的对象进行弱引用。std::weak_ptr 主要用于解决 std::shared_ptr 的循环引用问题。 

在 weakPtrExample 函数中: 定义了 A 和 B 两个类,其中 A 类包含一个 std::shared_ptr<B> 成员,B 类包含一个 std::weak_ptr<A> 成员。创建了 A 和 B 的 std::shared_ptr 对象,并相互引用。由于 B 类使用了 std::weak_ptr,所以不会出现循环引用,当 a 和 b 离开作用域时,对象能够被正确销毁。

Read more

Ubuntu24.04搭建GitLab服务器

Ubuntu24.04搭建GitLab服务器 * 简述 * 安装GitLab * 配置GitLab * 访问与初始化 * 修改默认密码 * 日常管理维护 * 数据备份 * 数据恢复 * 进阶配置(可选) * 使用方法简述 简述 GitLab是一个功能强大的DevOps平台,涵盖了从项目规划、源代码管理到持续集成、部署和监控的整个开发生命周期。下面这个流程图梳理了GitLab的核心功能模块和学习路径: 安装GitLab 1. 安装依赖包 sudoapt update sudoaptinstall -y curl openssh-server ca-certificates postfix * 在安装postfix(邮件服务器)时,可能会弹出配置窗口。如果你有域名并计划用于GitLab,可以选择"Internet Site"并设置域名;如果暂时不需要邮件功能或没有域名,也可以先跳过,后续再配置。 2. 添加GitLab软件仓库并安装 接下来,我们通过官方仓库安装GitLab。这里提供了官方源和国内镜像源两种方式,国内镜像通常速度

By Ne0inhk
【鸿蒙2025领航者闯关】从技术突破到生态共建,开发者的成长与远航

【鸿蒙2025领航者闯关】从技术突破到生态共建,开发者的成长与远航

文章目录 * 前言 * 第一章 鸿蒙开发入门:认知全场景操作系统的核心魅力 * 1.1 鸿蒙操作系统的核心定位 * 1.2 鸿蒙开发的核心技术底座 * 1.2.1 分布式技术:设备协同的“灵魂” * 1.2.2 ArkUI:全场景UI开发的“利器” * 1.2.3 鸿蒙应用的两种形态:FA与HAP * 第二章 技术成长突破:从单端开发到跨设备协同的蜕变 * 2.1 成长痛点:单端开发的“能力天花板” * 2.2 核心突破一:掌握ArkUI多端自适应开发 * 2.2.1 声明式编程的思维转变 * 2.2.2 多端自适应的核心技术:布局约束与媒体查询 * 2.

By Ne0inhk
Flutter for OpenHarmony: Flutter 三方库 dart_style 像官方一样统一你的鸿蒙代码格式(代码美化神器)

Flutter for OpenHarmony: Flutter 三方库 dart_style 像官方一样统一你的鸿蒙代码格式(代码美化神器)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 在 OpenHarmony 项目开发中,不论是个人的“心血之作”还是团队协作的“巨无霸”工程,代码的可读性是维护成本的生命线。每个人都有自己的编码习惯:有人喜欢紧凑型,有人喜欢在大括号前后留白。如果代码格式没有统一的标准,代码提交(Git Merge)时的差异对比将是一场灾难。 dart_style(其核心命令即 dart format)是 Dart 语言官方出品的格式化引擎。它通过一套被全球 Dart 开发者公认的算法,强制将你的源码重新排版为最标准、最易读的形态。 一、核心排版逻辑 dart_style 采用“行长度优先”的排版权重算法。 计算行长 修正空白 杂乱的源码 dart_style 解析器 折行与对齐策略

By Ne0inhk
Flutter 组件 cleany 适配鸿蒙 HarmonyOS 实战:自动化清理矩阵,构建复杂应用的状态闭环与资源防腐架构

Flutter 组件 cleany 适配鸿蒙 HarmonyOS 实战:自动化清理矩阵,构建复杂应用的状态闭环与资源防腐架构

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 cleany 适配鸿蒙 HarmonyOS 实战:自动化清理矩阵,构建复杂应用的状态闭环与资源防腐架构 前言 在鸿蒙(OpenHarmony)生态迈向多任务并行、长周期驻留及高频账户流转的全场景办公与生活背景下,如何确保应用在退出登录、环境切换或异常恢复时能够“不留痕迹”地销毁脏数据,已成为衡量应用健壮性的核心指标。在鸿蒙设备这类强调分布式沙箱隔离与严苛内存占用(Resident Set Size)管控的环境下,如果应用缺乏统一的资源清理机制,由于由于散落在各处的 Stream 监听、本地缓存及内存单例,极易由于由于状态残留导致不同用户间的数据越权或 UI 状态的逻辑死锁。 我们需要一种能够集中注册清理任务、支持并发异步销毁且具备原子性执行保障的状态复位框架。 cleany 为 Flutter 开发者引入了极其暴力且高效的“全域清算”范式。它通过中心化的管理器(Manager),允许各个业务模块在初始化时注册其对应的资源回收钩子。在适

By Ne0inhk