【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


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

点击跳转到网站

一、引入

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

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_ptrstd::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 离开作用域时,对象能够被正确销毁。

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

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

相关文章

Java多线程实现之线程池详解

Java多线程实现之线程池详解 一、线程池的基本概念1.1 为什么需要线程池1.2 线程池的核心思想 二、Java线程池的实现2.1 Executor框架2.2 ThreadPoolExecutor构造参数 三、常见线程池类型3.1 FixedThreadPool3.2 CachedThreadPool3.3 SingleThreadExecutor3.4 ScheduledThreadP…

解码美元-黄金负相关:LSTM-Attention因果发现与黄金反弹推演

摘要&#xff1a;本文采用时间序列分析框架与自然语言处理&#xff08;NLP&#xff09;技术&#xff0c;对黄金与美元指数的负相关关系进行量化拆解。通过构建包含宏观经济因子、市场情绪指标及地缘风险的三维分析模型&#xff0c;揭示当前贵金属市场的核心驱动逻辑&#xff0c…

Asp.Net Core SignalR导入数据

文章目录 前言一、安装包二、使用步骤1.实现SignalR Hub服务&#xff1a;2.实现CSV文件解析及数据导入服务3.控制器4.前端实现&#xff08;vue&#xff09; 三、关键技术点说明总结 前言 导入CSV文件中的数据到数据库&#xff0c;使用CsvHelper解析CSV文件&#xff0c;SqlBulk…

Modern C++(四)声明

4、声明 声明是将名字引入到cpp程序中&#xff0c;不是每条声明都声明实际的东西。定义是足以使该名字所标识的实体被使用的声明。声明包含以下几种&#xff1a; 函数定义模板声明模板显式实例化模板显式特化命名空间定义链接说明属性声明&#xff08;C11&#xff09;空声明&…

目标检测yolo算法

yolov5s&#xff1a; 从github官网下载yolov5的算法之后&#xff0c;配置好环境&#xff08;pycharm安装包-CSDN博客&#xff09;&#xff0c;再下载权重文件&#xff0c;比如默认的yolov5s.pt&#xff1b; 运行当前文件&#xff08;detect.py&#xff09;&#xff0c;就能看…

一个超强的推理增强大模型,开源了,本地部署

大家好&#xff0c;我是 Ai 学习的老章 前几天介绍了MOE 模型先驱 Mistral 开源的代码 Agent 大模型——mistralai/Devstral-Small-2505 今天一起看看 Mistral 最新开源的推理大模型——Magistral Magistral 简介 Mistral 公司推出了首个推理模型 Magistral 及自研可扩展强…

MySQL体系架构解析(五):读懂MySQL日志文件是优化与故障排查的关键

MySQL文件 日志文件 在服务器运行过程中&#xff0c;会产生各种各样的日志&#xff0c;比如常规的查询日志&#xff0c;错误日志、二进制日志、 redo 日志和 Undo 日志等&#xff0c;日志文件记录了影响 MySQL 数据库的各种类型活动。 常见的日志文件有&#xff1a;错误日志…

湖南省网络建设与运维赛项竞赛规程及样题

湖南省职业院校技能竞赛样题 赛题说明 一、竞赛内容 “网络建设与运维”竞赛共分三个部分&#xff0c;其中&#xff1a; 第一部分&#xff1a;职业规范与素养 &#xff08; 5 分&#xff09; 第二部分&#xff1a;网络搭建及安全部署项目 &#xff08; 50 分&#xff09…

华为云Flexus+DeepSeek征文 | 基于华为云ModelArts Studio搭建AnythingLLM聊天助手

华为云FlexusDeepSeek征文 | 基于华为云ModelArts Studio搭建AnythingLLM聊天助手 引言一、ModelArts Studio平台介绍华为云ModelArts Studio简介ModelArts Studio主要特点 二、AnythingLLM介绍AnythingLLM 简介AnythingLLM主要特点AnythingLLM地址 三、安装AnythingLLM应用下载…

板凳-------Mysql cookbook学习 (十--5)

6.11 计算年龄 2025年6月11日星期三 --创建表、初始化数据 drop table if exists sibling; create table sibling (name char(20),birth date );insert into sibling (name,birth) values(Gretchen,1942-04-14); insert into sibling (name,birth) values(Wilbur,1946-11-28)…

SAP RESTFUL接口方式发布SICF实现全路径

其他相关资料帖可参考&#xff1a; https://blog.csdn.net/woniu_maggie/article/details/146210752 https://blog.csdn.net/SAPmatinal/article/details/134349125 https://blog.csdn.net/weixin_44382089/article/details/128283417 【业务场景】 外部系统不想通过RFC (需…

在windows中安装或卸载nginx

首先在nginx的安装目录下cmd查看nginx的版本&#xff1a; 在看windows的服务中是否nginx注册为服务了 如果注册了服务就先将服务卸载了 在nginx的安装目录cmd执行命令 NginxService.exe uninstall “NginxService”是对应的注册的服务名称 关闭所有的相关nginx的服务这个也…

FaceFusion 技术深度剖析:核心算法与实现机制揭秘

在 AI 换脸技术蓬勃发展的浪潮中&#xff0c;FaceFusion 凭借其出色的换脸效果和便捷的操作&#xff0c;成为众多用户的首选工具。从短视频平台上的创意恶搞视频&#xff0c;到影视制作中的特效合成&#xff0c;FaceFusion 都展现出强大的实用性。而这一切的背后&#xff0c;是…

2. Web网络基础 - 协议端口

深入解析协议端口与netstat命令&#xff1a;网络工程师的实战指南 在网络通信中&#xff0c;协议端口是服务访问的门户。本文将全面解析端口概念&#xff0c;并通过netstat命令实战演示如何监控网络连接状态。 一、协议端口核心知识解析 1. 端口号的本质与分类 端口范围类型说…

嵌入式学习笔记 - freeRTOS vTaskPlaceOnEventList()函数解析

vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait ); 函数第一个参数为消息队列等待插入链表&#xff0c; void vTaskPlaceOnEventList( List_t * const pxEventList, const TickType_t xTicksToWait ) { configASSERT( pxEventList ); /…

Ubuntu 配置使用 zsh + 插件配置 + oh-my-zsh 美化过程

Ubuntu 配置使用 zsh 插件配置 oh-my-zsh 美化过程 引言zsh 安装及基础配置oh-my-zsh 安装及美化配置oh-my-zsh 安装主题美化配置主题自定义主题 插件安装及配置官方插件查看及启用插件安装 主题文件备份.zshrcre5et_self.zsh-theme 同步发布在个人笔记Ubuntu 配置使用 zsh …

Xilinx FPGA 重构Multiboot ICAPE2和ICAPE3使用

一、FPGA Multiboot 本文主要介绍基于IPROG命令的FPGA多版本重构&#xff0c;用ICAP原语实现在线多版本切换。需要了解MultiBoot Fallback点击链接。 如下图所示&#xff0c;ICAP原语可实现flash中n1各版本的动态切换&#xff0c;在工作过程中&#xff0c;可以通过IPROG命令切…

springMVC-11 中文乱码处理

前言 本文介绍了springMVC中文乱码的解决方案&#xff0c;同时也贴出了本人遇到过的其他乱码情况&#xff0c;可以根据自身情况选择合适的解决方案。 其他-jdbc、前端、后端、jsp乱码的解决 Tomcat导致的乱码解决 自定义中文乱码过滤器 老方法&#xff0c;通过javaW…

mysql-innoDB存储引擎事务的原理

InnoDB 存储引擎支持 ACID 事务&#xff0c;其事务机制是通过 Redo Log&#xff08;重做日志&#xff09;、Undo Log&#xff08;回滚日志&#xff09; 和 事务日志系统 来实现的。下面详细解析 InnoDB 事务的工作原理。 1.事务的基本特性&#xff08;ACID&#xff09; 特性描…

在GIS 工作流中实现数据处理

通过将 ArcPy 应用于实际的 GIS 工作流&#xff0c;我们可以高效地完成数据处理任务&#xff0c;节省大量时间和精力。接下来&#xff0c;本文将结合具体案例&#xff0c;详细介绍如何运用 ArcPy 实现 GIS 数据处理的全流程。 数据读取与合并 假设我们有多个 shapefile 文件&a…