【C++ 】智能指针:内存管理的 “自动导航仪”
目录
一、引入
二、智能指针的两大特性:
1、RAII
特点:
好处:
2、行为像指针
三、智能指针起初的缺陷:拷贝问题
四、几种智能指针的介绍。
1、C++98出现的智能指针——auto_ptr
auto_ptr解决上述拷贝构造的问题:
2、boost库
3、unique_ptr
引用计数的实现:
赋值运算符的问题:(循环引用)
5、weak_ptr
特点:
解决循环引用问题:
五、C++智能指针的基本框架:
六、定制删除器,以及包装器的使用场景之一
七、内存泄漏:
1、什么是内存泄漏,内存泄漏的危害:
2、内存泄漏的分类
八、关于C++智能指针的相关代码:
std::unique_ptr
std::weak_ptr
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家
点击跳转到网站
一、引入
首先通过一个使用场景来引入智能指针,如下:
class HF { public:HF(int a, int b):_a(a),_b(b){cout << "HF(int a, int b)" << endl;};~HF(){cout << "~HF()" << endl;} private:int _a = 0;int _b = 1; };void fun() {HF* h1 = new HF(1, 1);HF* h2 = new HF(1, 1);HF* h3 = new HF(1, 1);delete h1;delete h2;delete h3; }int main() {try{fun();}catch (exception& e){cout << e.what() << endl;}return 0; }
这里有一个类HF,一个子函数fun,在fun里面new了三个HF对象,然后delete,正常情况下delete会先调用析构函数,然后再释放相应的资源:
二、智能指针的两大特性:
智能指针的两大特性:
1、RAII
2、行为像指针
1、RAII
是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。(通俗来讲,就是将资源交给一个类对象来管理,通过该类的构造函数交给对象。)
特点:
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。
好处:
(1)、不需要显式地释放资源,而是通过智能指针间接帮忙释放(2)、采用这种方式,对象所需的资源在其生命期内始终保持有效
2、行为像指针
智能指针实际也是一个类,要是行为像一个指针,即要重载解引用(*),箭头(->),甚至有时还要重载方括号([ ])。
三、智能指针起初的缺陷:拷贝问题
首先我们实现一个简易版智能指针:
new了一个日期类对象交给智能指针管理,智能指针对象存在期间,资源都是存在的,最后智能指针对象生命周期结束,调用析构函数释放,同时释放掉资源(delete);
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; }; struct Date {int _year;int _month;int _day; }; int main() {SmartPtr<int> sp1(new int);*sp1 = 10;cout << *sp1 << endl;SmartPtr<Date> sparray(new Date);// 需要注意的是这里应该是sparray.operator->()->_year = 2018;// 本来应该是sparray->->_year这里语法上为了可读性,省略了一个->sparray->_year = 2018;sparray->_month = 1;sparray->_day = 1;return 0; }
但是当我们用对象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
#include <iostream> #include <memory>// 自定义类 class MyClass { public:MyClass() { std::cout << "MyClass constructor" << std::endl; }~MyClass() { std::cout << "MyClass destructor" << std::endl; }void doSomething() { std::cout << "Doing something..." << std::endl; } };class B;class A { public:std::shared_ptr<B> bPtr;~A() { std::cout << "A destructor" << std::endl; } };class B { public:std::weak_ptr<A> aPtr; // 使用 weak_ptr 避免循环引用~B() { std::cout << "B destructor" << std::endl; } };void uniquePtrExample() {// 创建一个 unique_ptr 指向 MyClass 对象std::unique_ptr<MyClass> uniquePtr = std::make_unique<MyClass>();// 调用对象的成员函数uniquePtr->doSomething();// unique_ptr 不能被复制,但可以转移所有权// std::unique_ptr<MyClass> anotherPtr = uniquePtr; // 错误,不能复制std::unique_ptr<MyClass> anotherPtr = std::move(uniquePtr);if (!uniquePtr) {std::cout << "uniquePtr is empty after move" << std::endl;}if (anotherPtr) {anotherPtr->doSomething();} }void sharedPtrExample() {// 创建一个 shared_ptr 指向 MyClass 对象std::shared_ptr<MyClass> sharedPtr1 = std::make_shared<MyClass>();std::cout << "Shared pointer 1 use count: " << sharedPtr1.use_count() << std::endl;// 复制 shared_ptr,引用计数增加std::shared_ptr<MyClass> sharedPtr2 = sharedPtr1;std::cout << "Shared pointer 1 use count after copy: " << sharedPtr1.use_count() << std::endl;std::cout << "Shared pointer 2 use count: " << sharedPtr2.use_count() << std::endl;// 调用对象的成员函数sharedPtr2->doSomething();// 释放一个 shared_ptr,引用计数减少sharedPtr2.reset();std::cout << "Shared pointer 1 use count after reset sharedPtr2: " << sharedPtr1.use_count() << std::endl; }void weakPtrExample() {std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->bPtr = b;b->aPtr = a;std::cout << "A use count: " << a.use_count() << std::endl;std::cout << "B use count: " << b.use_count() << std::endl;// 当 a 和 b 离开作用域时,对象会被正确销毁 }int main() {std::cout << "=== Unique Ptr Example ===" << std::endl;uniquePtrExample();std::cout << std::endl;std::cout << "=== Shared Ptr Example ===" << std::endl;sharedPtrExample();std::cout << std::endl;std::cout << "=== Weak Ptr Example ===" << std::endl;weakPtrExample();std::cout << std::endl;return 0; }
在 C++ 里,手动管理动态分配的内存容易引发内存泄漏、悬空指针等问题。智能指针作为一种类模板,能有效管理动态分配的内存,避免这些问题的出现。C++ 标准库提供了三种主要的智能指针:
std::unique_ptr
、std::shared_ptr
和std::weak_ptr
。
std::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
离开作用域时,对象能够被正确销毁。
相关文章:

【C++ 】智能指针:内存管理的 “自动导航仪”
目录 一、引入 二、智能指针的两大特性: 1、RAII 特点: 好处: 2、行为像指针 三、智能指针起初的缺陷:拷贝问题 四、几种智能指针的介绍。 1、C98出现的智能指针——auto_ptr auto_ptr解决上述拷贝构造的问题:…...

设备制造行业项目管理难点解析,如何有效解决?
在设备制造行业,项目管理是企业运营的核心环节,直接影响项目交付效率、成本控制和盈利能力。然而,由于行业特性复杂、项目周期长、涉及部门多,企业在实际操作中常常面临诸多管理痛点。金众诚工程项目管理系统,依托金蝶…...

浅谈 PAM-2 到 PAM-4 的信令技术演变
通信信令技术演进:从 PAM-2 到 PAM-4 在当今数字化高速发展的时代,数据传输需求呈爆炸式增长,行业对通信带宽的要求愈发严苛。为顺应这一趋势,通信信令技术不断革新,曾经占据主导地位的不归零(NRZÿ…...

Protos-SIP:经典 SIP 协议模糊测试工具!全参数详细教程!Kali Linux教程!
简介 该测试套件的目的是评估会话发起协议 (SIP) 实现的实现级别安全性和稳健性。 Protos-SIP 是一款专为 SIP 协议模糊测试(Fuzzing)设计的工具,最初由 OUSPG(Oulu University Secure Programming Group)开发&#…...

复数三角不等式简介及 MATLAB 演示
复数三角不等式简介及 MATLAB 演示 1. 复数三角不等式简介 复数三角不等式(Complex Triangle Inequality)是复数的一种重要性质,它类似于普通的三角不等式,但适用于复数空间。具体来说,复数三角不等式可以描述复数之…...

【Doris基础】Apache Doris 基本架构深度解析:从存储到查询的完整技术演进
目录 1 引言 2 Doris 架构全景图 2 核心组件技术解析 2.1 Frontend 层(FE) 2.2 Backend 层(BE) 3 数据存储与复制机制 3.1 存储架构演进 3.2 副本复制策略 4 查询处理全流程解析 4.1 查询生命周期 5 高可用设计 5.1 F…...

程序人生-hellohelloo
计算机系统 大作业 题 目 程序人生-Hello’s P2P 专 业 计算机与电子通信 学 号 2023111976 班 级 23L0504 学 生 孙恩旗 指 导 教 师 刘宏伟 计算机科…...

ASP.NET Core SignalR的基本使用
文章目录 前言一、SignalR是什么?在 ASP.NET Core 中的关键特性:SignalR 工作原理简图: 二、使用步骤1.创建ASP.NET Core web Api 项目2.添加 SignalR 包3.创建 SignalR Hub4.配置服务与中间件5.创建控制器(模拟服务器向客户端发送消息)6.创建…...

【C语言】讲解 程序分配的区域(新手)
目录 代码区 数据区 堆区 栈区 常量区 重点比较一下堆区与 栈区 总结: 前言: C语言程序的内存分配区域是理解其运行机制的重要部分。根据提供的多条证据,我们可以总结出C语言程序在运行时主要涉及以下五个关键内存区域: 代…...
【脚本 完全参数化的通用 APT 源配置方案-Debian/Ubuntu】
通过脚本在 Debian/Ubuntu 系统上一键切换 APT 源 如Dockerfile中 使用某个源(比如 aliyun) 假设你的目录结构是: . ├── Dockerfile └── switch-apt-source.shFROM ubuntu:22.04# 把脚本拷贝到镜像中 COPY switch-apt-source.sh /us…...
数据集笔记:SeekWorld
提出了一项新任务:地理定位推理(Geolocation Reasoning) 该任务要求模型在感知视觉信息的同时,推理出图像中视觉语义所隐含的高级逻辑关系,从而确定图像的拍摄地点 TheEighthDay/SeekWorld at main 构建了一个基于规则…...

LeetCode 算 法 实 战 - - - 移 除 链 表 元 素、反 转 链 表
LeetCode 算 法 实 战 - - - 移 除 链 表 元 素、反 转 链 表 第 一 题 - - - 移 除 链 表 元 素方 法 一 - - - 原 地 删 除方 法 二 - - - 双 指 针方 法 三 - - - 尾 插 第 二 题 - - - 反 转 链 表方 法 一 - - - 迭 代方 法 二 - - - 采 用 头 插 创 建 新 链 表 总 结 &a…...
Jenkins实践(10):pipeline构建历史展示包名和各阶段间传递参数
Jenkins实践(10):构建历史展示包名和pipeline各阶段间传递参数 1、构建历史展示包名 参考:https://blog.csdn.net/fen_fen/article/details/148167868 1.1、方法说明 Jenkins版本:Jenkins2.452 通过修改 currentBuild.displayName 和 currentBuild.description 实现: …...
从头认识AI-----循环神经网络(RNN)
前言 前面我们讲了传统的神经网络,如MLP、CNN,这些网络中的输入都被单独处理,没有上下文之间的信息传递机制,这在处理序列数据(如语音、文本、时间序列)时很鸡肋: 如何理解一句话中“前后文”的…...
配置远程无密登陆ubuntu服务器时无法连接问题排查
配置远程无密登陆ubuntu服务器时无法连接问题排查 登陆端排查服务器端登陆排查 登陆端排查 ssh -v 用户名Ubuntu服务器IP可能日志输出 debug1: Authentications that can continue: publickey,password服务器端登陆排查 sudo tail -f /var/log/auth.log可能日志输出 Authen…...
5.31 数学复习笔记 22
前面的笔记,全部写成一段,有点难以阅读。现在改进一下排版。另外,写笔记实际上就是图一个放松呢,关键还是在于练习。 目前的计划是,把讲义上面的高数例题搞清楚之后,大量刷练习册上面的题。感觉不做几本练…...

kafka学习笔记(三、消费者Consumer使用教程——使用实例及及核心流程源码讲解)
1.核心概念与架构 1.1.消费者与消费者组 Kafka消费者是订阅主题(Topic)并拉取消息的客户端实例,其核心逻辑通过KafkaConsumer类实现。消费者组(Consumer Group)是由多个逻辑关联的消费者组成的集合。 核心规则 同一…...

鸿蒙 Form Kit(卡片开发服务)
Form Kit(卡片开发服务) 鸿蒙应用中,Form / Card / Widget 都翻译为“卡片” Form Kit(卡片开发服务)提供一种界面展示形式,可以将应用的重要信息或操作前置到服务卡片,以达到服务直达、减少跳转…...

算力卡上部署OCR文本识别服务与测试
使用modelscope上的图像文本行检测和文本识别模型进行本地部署并转为API服务。 本地部署时把代码中的检测和识别模型路径改为本地模型的路径。 关于模型和代码原理可以参见modelscope上这两个模型相关的页面: iic/cv_resnet18_ocr-detection-db-line-level_damo iic…...

KWIC—Implicit Invocation
KWIC—Implicit Invocation ✏️ KWIC—Implicit Invocation 文章目录 KWIC—Implicit Invocation📝KWIC—Implicit Invocation🧩KWIC🧩核心组件🧩ImplementationScheme⚖️ 隐式调用 vs 显式调用对比 🌟 总结 &#x…...

Visual Studio 2022 发布独立的 exe 文件
我们在用 Visual Studio 2022 写好一个 exe 程序之后,如果想把这个拿到其他地方运行,需要把 exe 所在的文件夹一起拿过去。 编译出来的 exe 文件需要其他几个文件一同放在同一目录才能运行,原因在于默认情况下,Visual Studio 是把…...

11.4java语言执行浅析4
编译成字节码(.class 文件) 使用 javac 命令将源代码编译为 Java 字节码(bytecode) 它不是机器码,而是 JVM 能理解的中间语言(字节码),具有平台无关性。 编译过程简要࿱…...
Excel 操作 转图片,转pdf等
方式一 spire.xls.free(没找设置分辨率的方法) macOs开发Java GUI程序提示缺少字体问题解决 Spire.XLS:一款Excel处理神器_spire.xls免费版和收费版的区别-CSDN博客 官方文档 Spire.XLS for Java 中文教程 <dependency><groupI…...
说说 Kotlin 中的 Any 与 Java 中的 Object 有何异同?
在 Kotlin 中 Any 类型和 Java 中的 Object 类都是所有类型的根类型。 1 基本定义 Kotlin 中的 Any 和 Any?: Any:是所有非空类型的根类型;Any?:是所有可空类型的根类型; Java 中的 Object: 是所有类…...

python分配方案数 2023年信息素养大赛复赛/决赛真题 小学组/初中组 python编程挑战赛 真题详细解析
python分配方案数 2023全国青少年信息素养大赛Python编程挑战赛复赛真题解析 博主推荐 所有考级比赛学习相关资料合集【推荐收藏】1、Python比赛 信息素养大赛Python编程挑战赛 蓝桥杯python选拔赛真题详解...

《信号与系统》第 5 章 离散时间傅里叶变换
5.0 引言 这一章将介绍并研究离散时间傅里叶变换,这样就完整地建立了傅里叶分析方法。 5.1 非周期信号的表示:离散时间傅里叶变换 5.1.1 离散时间傅里叶变换的导出 在第4章看到:一个连续时间周期方波的傅里叶级数可以看成一个包络函数的采…...

动态IP与区块链:重构网络信任的底层革命
在数字经济蓬勃发展的今天,网络安全与数据隐私正面临前所未有的挑战。动态IP技术与区块链的深度融合,正在构建一个去中心化、高可信的网络基础设施,为Web3.0时代的到来奠定基础。 一、技术碰撞:动态IP与区块链的天然契合 动态I…...
目前主流图像分类模型的详细对比分析
以下是目前主流图像分类模型的详细对比分析,结合性能、架构特点及应用场景进行整理: 一、主流模型架构分类与定量对比 模型名称架构类型核心特点ImageNet Top-1准确率参数量(百万)计算效率典型应用场景ResNetCNN残差连接解决梯度…...

uniapp使用Canvas生成电子名片
uniapp使用Canvas生成电子名片 工作中有生成电子名片的一个需求,刚刚好弄了发一下分享分享 文章目录 uniapp使用Canvas生成电子名片前言一、上代码?总结 前言 先看效果 一、上代码? 不对不对应该是上才艺,哈哈哈 <template…...

世冠科技亮相中汽中心科技周MBDE会议,共探汽车研发数字化转型新路径
近日,中汽中心2025年科技周MBDE前沿应用主题会议在天津成功举办。本次会议以“智汇津门共探MBDE前沿应用新征程”为主题,聚焦基于模型的数字工程(MBDE)方法论在汽车复杂系统研发中的创新实践与跨领域协同,旨在推动行业…...