【C++】深入解析C++智能指针:从auto_ptr到unique_ptr与shared_ptr

文章目录

  • 前言:
  • 1. 智能指针的使用及原理
  • 2. C++ 98 标准库中的 auto_ptr:
  • 3. C++ 11 中的智能指针
    • 循环引用:
    • shared_ptr 定制删除器
  • 4. 内存泄漏
  • 总结:

前言:

随着C++语言的发展,智能指针作为现代C++编程中管理动态分配内存的一种重要工具,越来越受到开发者的青睐。智能指针不仅简化了内存管理,还有助于避免内存泄漏等常见问题。本文将深入探讨智能指针的使用及其原理,从C++98标准库中的auto_ptr开始,逐步过渡到C++11中更为强大和灵活的智能指针类型,如unique_ptrshared_ptr。此外,文章还将讨论循环引用问题、内存泄漏的原因及其危害,并提供相应的解决方案。通过本文的学习,读者将能够更好地理解和运用智能指针,编写出更安全、更高效的C++代码。

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

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

  • 不需要显式地释放资源。
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效。
// SmartPtr.h
// 使用RAII思想设计的smartPtr类template<class T>
class SmartPtr {
public:SmartPtr(T* ptr = nullptr):_ptr(ptr){}~SmartPtr(){if (_ptr) {std::cout << "delete: " << _ptr << std::endl;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()
{ShardPtr<int> sp1(new int);ShardPtr<int> sp2(new int);cout << div() << endl;
}int main()
{try {Func();}catch(const exception& e){cout<<e.what()<<endl;}return 0;
}
//test.cpp
#include <iostream>
#include "SmartPtr.h"
using namespace std;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> sp2(new int);cout << div() << endl; 
}int main()
{try {Func();}catch (const exception& e){cout << e.what() << endl;}return 0;
}

在这里插入图片描述

  • 需要像指针一样的去使用:
// 像指针一样使用
T& operator*()
{return *_ptr;
}T* operator->()
{return _ptr;
}
SmartPtr<int> sp1(new int(1));
SmartPtr<int> sp2(new int(0));
*sp1 += 10;SmartPtr<pair<string, int>> sp3(new pair<string, int>);
sp3->first = "apple";
sp3->second = 1; // 等价于 sp3.opertor->()->second = 1;cout << sp3->first << " " << sp3->second << endl;
  • 智能指针的拷贝问题
// 智能指针的拷贝问题
int main()
{SmartPtr<int> sp1(new int(1));SmartPtr<int> sp2(sp1);return 0;
}

在这里插入图片描述

vector / list.… 需要深拷贝,它们都是利用资源存储数据,资源是自己的。拷贝时,每个对象各自一份资源,各管各的,所以深拷贝。

智能指针 / 迭代器… 期望的是浅拷贝
资源不是自己的,代为持有,方便访问修改数据。他们拷贝的时候期望的指向同一资源,所以浅拷贝。而且智能指针还要负责释放资源。

itertor it = begin();

2. C++ 98 标准库中的 auto_ptr:

auto_ptr 管理权转移,被拷贝的对象把资源管理权转移给拷贝对象,导致被拷贝对象悬空
注意:在使用auto_ptr 过后不能访问对象,否则就出现空指针了。很多公司禁止使用它,因为他很坑!

 // 智能指针的拷贝问题
// 1. auto_ptr 管理权转移,被拷贝的对象把资源管理权转移给拷贝对象,导致被拷贝对象悬空
// 注意:在使用auto_ptr 过后不能访问对象,否则就出现空指针了。很多公司禁止使用它,因为他很坑!
int main()
{std::auto_ptr<int> sp1(new int(1));std::auto_ptr<int> sp2(sp1);*sp2 += 10;// 悬空*sp1 += 10;return 0;
}

auto_ptr 的实现:

namespace hd
{template<class T>class auto_ptr {public:// RAIIauto_ptr(T* ptr = nullptr):_ptr(ptr){}// ap2(ap1)auto_ptr(auto_ptr<T>& ap){_ptr = ap._ptr;ap._ptr = nullptr;}~auto_ptr(){if (_ptr) {std::cout << "delete: " << _ptr << std::endl;delete _ptr;                                                             }}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
}

3. C++ 11 中的智能指针

boost 智能指针
scoped_ptr / scoped_array
shared_ptr / shared_array

C++ 11
unique_ptrscoped_ptr类似的
shared_ptrshared_ptr类似的

unique_ptr
禁止拷贝,简单粗暴,适合于不需要拷贝的场景
在这里插入图片描述
赋值也禁掉了:
在这里插入图片描述
unique_ptr:实现

namespace hd
{template<class T>class unique_ptr {public:// RAIIunique_ptr(T* ptr = nullptr):_ptr(ptr){}// ap2(ap1)unique_ptr(const unique_ptr<T>& ap) = delete;  // 禁掉拷贝构造// 赋值也要禁掉,赋值会生成默认成员函数,浅拷贝,也会出现问题unique_ptr<T>& operator=(const unique_ptr<T>& ap) = delete;~unique_ptr(){if (_ptr) {std::cout << "delete: " << _ptr << std::endl;delete _ptr;}}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
}

如果必须要拷贝用shared_ptr:
shared_ptr 允许自由拷贝,使用引用计数解决多次释放的问题

引用计数: 记录有几个对象参与管理这个资源
在这里插入图片描述
shared_ptr 实现:
使用静态成员变量实现。

namespace hd
{template<class T>class shared_ptr {public:// RAIIshared_ptr(T* ptr = nullptr):_ptr(ptr){_count = 1;}// sp(sp1)shared_ptr(const shared_ptr<T>& sp){_ptr = sp._ptr;++_count;}~shared_ptr(){if (--_count == 0){std::cout << "delete:" << _ptr << std::endl;delete _ptr;}}int use_count(){return _count;}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;static int _count;};template<class T>int shared_ptr<T>::_count = 0;
}

在这里插入图片描述
中释放了一个资源!
如果使用静态成员属于这个类,属于这个类的所有对象
需求:每个资源配一个引用计数,而不是全部都是一个引用计数!

所以,一个资源配一个引用计数无论多少个对象管理这个资源,只有这一个计数对象!
怎么找到这个引用呢?每个对象存一个指向计数的指针!

namespace hd
{template<class T>class shared_ptr {public:// RAIIshared_ptr(T* ptr = nullptr): _ptr(ptr), _pcount(new int(1)){}// sp2(sp1)shared_ptr(const shared_ptr<T>& sp){_ptr = sp._ptr;_pcount = sp._pcount;// 拷贝时++计数++(*_pcount);}void release(){// 说明最后一个管理对象析构了,可以释放资源了if (--(*_pcount) == 0){std::cout << "delete:" << _ptr << std::endl;delete _ptr;delete _pcount;}}// 赋值 sp1 = sp3;shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr) // 避免自己给自己赋值{release();_ptr = sp._ptr;_pcount = sp._pcount;// 拷贝时++计数++(*_pcount);}return *this;}~shared_ptr(){release();}int use_count(){return *_pcount;}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;int* _pcount;};}

在这里插入图片描述

shared_ptr 的缺陷:

// shared_ptr 的缺陷
struct ListNode
{int _val;std::shared_ptr<ListNode> _next;std::shared_ptr<ListNode> _prev;ListNode(int val = 0):_val(val),_next(nullptr),_prev(nullptr){}};int main()
{std::shared_ptr<ListNode> n1(new ListNode(10));std::shared_ptr<ListNode> n2(new ListNode(20));n1->_next = n2;n2->_prev = n1;//delete n1;//delete n2;return 0;
}

循环引用:

  1. 左边的节点,是由右边的节点_prev管着的,_prev析构,引用计数减到 0, 左边的节点就是释放
  2. 右边节点中_prev 什么时候析构呢?右边的节点被delete时,_prev 析构。
  3. 右边节点什么时候delete呢?右边的节点被左边的节点的_next管着的,_next析构,右边的节点就释放了。
  4. _next 什么时候析构呢?_next 是左边节点的成员,左边节点 delete, _next 就析构了
  5. 左边节点什么时候释放呢?回调 1 点 又循环上去了

右边节点释放 -> _prev析构 -> 左边节点的释放 -> _next析构 -> 右边节点释放

所以这是 shared_ptr 特定场景下的缺陷, 只要有两个shared_ptr 互相管理就会出现这样的情况,所以即使用了智能指针,同样可能导致内存的泄漏。

struct ListNode
{int _val;std::weak_ptr<ListNode> _next;std::weak_ptr<ListNode> _prev;ListNode(int val = 0):_val(val){}};int main()
{std::shared_ptr<ListNode> n1(new ListNode(10));std::shared_ptr<ListNode> n2(new ListNode(20));cout << n1.use_count() << endl;cout << n2.use_count() << endl;n1->_next = n2;n2->_prev = n1;cout << n1.use_count() << endl;cout << n2.use_count() << endl;//delete n1;//delete n2;return 0;
}

在这里插入图片描述
weak_ptr 可以通过不增加引用计数的方式,避免这个问题。(存在单独自己的 引用计数)
weak_ptr 不支持RAII, 不参与资源管理,不支持指针初始化,但是还是能起到指向你的作用
weak_ptr 的实现:

namespace hd
{template<class T>class weak_ptr{public:weak_ptr():_ptr(nullptr){}weak_ptr(const shared_ptr<T>& sp){_ptr = sp._ptr;}weak_ptr<T>& operator=(const shared_ptr<T>& sp){  _ptr = sp.get(); // 用 get方法调原生指针}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
}

shared_ptr 定制删除器

template<class T>
struct DeleteArry
{void operator()(T* ptr){delete[] ptr;}
};// 定制删除器
int main()
{std::shared_ptr<ListNode> p1(new ListNode(10));std::shared_ptr<ListNode[]> p2(new ListNode[10]); // 可以用数组的std::shared_ptr<ListNode> p2(new ListNode[10], DeleteArry<ListNode>()); // 用仿函数的对象去释放!std::shared_ptr<FILE> p3(fopen("test.cpp", "r"), [](FILE* ptr) {fclose(ptr);  }); // 用lamada表达式也是可以的return 0;
}

在这里插入图片描述

定制删除器实现:

namespace hd
{template<class T>class shared_ptr{public:// function<void(T*)> _del = [](T* ptr) {delete ptr; };template<class D>shared_ptr(T* ptr, D del):_ptr(ptr), _pcount(new int(1)), _del(del){}// RAIIshared_ptr(T* ptr = nullptr):_ptr(ptr), _pcount(new int(1)){}// sp2(sp1)shared_ptr(const shared_ptr<T>& sp){_ptr = sp._ptr;_pcount = sp._pcount;// 拷贝时++计数++(*_pcount);}// sp1 = sp4// sp4 = sp4;// sp1 = sp2;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;}void release(){// 说明最后一个管理对象析构了,可以释放资源了if (--(*_pcount) == 0){std::cout << "delete:" << _ptr << std::endl;//delete _ptr;_del(_ptr);delete _pcount;}}~shared_ptr(){// 析构时,--计数,计数减到0,release();}int use_count(){return *_pcount;}// 像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}private:T* _ptr;int* _pcount;std::function<void(T*)> _del = [](T* ptr) {delete ptr; };};}

4. 内存泄漏

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

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

总结:

本文详细介绍了智能指针的概念、使用和原理,从C++98的auto_ptr到C++11的unique_ptrshared_ptr,展示了智能指针在现代C++编程中的应用和发展。我们了解到RAII(资源获取即初始化)的设计模式,它通过将资源管理封装在对象的生命周期中,简化了资源的获取和释放过程。文章还讨论了智能指针的拷贝问题,特别是auto_ptr的缺陷和shared_ptr的循环引用问题,以及如何使用weak_ptr和定制删除器来解决这些问题。

此外,文章还探讨了内存泄漏的概念、原因和危害,以及如何在实际编程中避免这些问题。通过具体的例子和代码,我们学习了如何使用智能指针来管理资源,确保资源在使用完毕后能够被正确释放,从而避免内存泄漏和其他潜在的资源管理问题。

总的来说,智能指针是C++中一个强大的特性,它不仅提高了代码的安全性和效率,还使得资源管理变得更加简单和直观。通过本文的学习,读者应该能够更加自信地在C++项目中使用智能指针,编写出更加健壮和可靠的软件。

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

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

相关文章

Win32 API

个人主页&#xff1a;星纭-CSDN博客 系列文章专栏 : C语言 踏上取经路&#xff0c;比抵达灵山更重要&#xff01;一起努力一起进步&#xff01; 一.Win32 API 1.Win32 API介绍 Windows这个多作业系统除了协调应⽤程序的执⾏、分配内存、管理资源之外&#xff0c;它同时也是…

python中的线程并行

文章目录 1. 单线程2. 线程池ThreadPoolExecutor 1. 单线程 现在有1154张图片需要顺时针旋转后保存到本地&#xff0c;一般使用循环1154次处理&#xff0c;具体代码如下所示&#xff0c;img_paths中存储1154个图片路径&#xff0c;该代码段耗时约用97ms。 t1time.time() for …

Windows安装VMware(Broadcom)

1.安装前提 1.检查BIOS中是否开启了虚拟化技术。1.1 打开任务管理器&#xff0c;查看性能&#xff0c;CPU部分&#xff0c;虚拟化处于“已启用”状态。1.2 如果没有开启&#xff0c;则需要进入BIOS系统&#xff0c;将 Intel Virtualization Technology改为Enalble。2.下载VMwa…

ROS2入门21讲__第19讲__Rviz:三维可视化显示平台

目录 前言 Rviz三维可视化平台 Rviz介绍 运行方法 彩色相机仿真与可视化 仿真插件配置 运行仿真环境 图像数据可视化 三维相机仿真与可视化 仿真插件配置 运行仿真环境 点云数据可视化 激光雷达仿真与可视化 仿真插件配置 运行仿真环境 点云数据可视化 Rviz v…

【HCIP学习】RSTP和MSTP

一、RSTP&#xff08;Rapid Spanning Tree Protocol&#xff0c;快速生成树&#xff09; 1、背景&#xff1a;RSTP从STP发展而来&#xff0c;具备STP的所有功能&#xff0c;可以兼容stp运行 2、RSTP与STP不同点 &#xff08;1&#xff09;减少端口状态 STP:disabled\blockin…

【Python搞定车载自动化测试】——Python实现CAN总线Bootloader刷写(含Python源码)

系列文章目录 【Python搞定车载自动化测试】系列文章目录汇总 文章目录 系列文章目录&#x1f4af;&#x1f4af;&#x1f4af; 前言&#x1f4af;&#x1f4af;&#x1f4af;一、环境搭建1.软件环境2.硬件环境 二、目录结构三、源码展示1.诊断基础函数方法2.诊断业务函数方法…

《最新出炉》系列入门篇-Python+Playwright自动化测试-40-录制生成脚本

宏哥微信粉丝群&#xff1a;https://bbs.csdn.net/topics/618423372 有兴趣的可以扫码加入 1.简介 各种自动化框架都会有脚本录制功能&#xff0c; playwright这么牛叉当然也不例外。很早之前的selenium、Jmeter工具&#xff0c;发展到每种浏览器都有对应的录制插件。今天我们…

python机器学习及深度学习在空间模拟与时间预测

原文链接https://mp.weixin.qq.com/s?__bizMzUyNzczMTI4Mg&mid2247628504&idx2&sn6fe3aeb9f63203cfe941a6bb63b49b85&chksmfa77a9e5cd0020f3aa4f01887e75b15096a182c2b5b42c1044787aa285c650f1469a0ef28aec&token2124656491&langzh_CN&scene21#we…

C++语法|虚函数与多态详细讲解(六)|如何解释多态?(面试向)

系列汇总讲解&#xff0c;请移步&#xff1a; C语法&#xff5c;虚函数与多态详细讲解系列&#xff08;包含多重继承内容&#xff09; 多态分为了两种&#xff0c;一种是静态的多态&#xff0c;一种是动态的多态。 静态&#xff08;编译时期&#xff09;的多态 函数重载 boo…

基于51单片机温度报警系统—数码管显示

基于51单片机温度报警系统 &#xff08;仿真&#xff0b;程序&#xff0b;原理图&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 1.DS18B20采集温度&#xff0c;数码管显示温度&#xff1b; 2.温度测量范围&#xff1a;0-99度&#xff1b; 3.当温度低于…

NDIS小端口驱动开发(三)

微型端口驱动程序处理来自过度驱动程序的发送请求&#xff0c;并发出接收指示。 在单个函数调用中&#xff0c;NDIS 微型端口驱动程序可以指示具有多个接收 NET_BUFFER_LIST 结构的链接列表。 微型端口驱动程序可以处理对每个NET_BUFFER_LIST结构上具有多个 NET_BUFFER 结构的多…

阻塞信号集和未决信号集_代码实现

1. 程序验证内容 将编号为0,1,2添加到阻塞信号集中&#xff0c;i<信号编号时&#xff0c;发出信号&#xff0c;观察未决信号集状态 当解除阻塞后&#xff0c;原先的信号是否执行&#xff0c;执行顺序是什么 2. 代码实现 #include <unistd.h> #include <stdlib.h…

【全开源】海报在线制作系统源码(ThinkPHP+FastAdmin+UniApp)

打造个性化创意海报的利器 引言 在数字化时代&#xff0c;海报作为一种重要的宣传媒介&#xff0c;其设计质量和效率直接影响着宣传效果。为了满足广大用户对于个性化、高效制作海报的需求&#xff0c;海报在线制作系统源码应运而生。本文将详细介绍海报在线制作系统源码的特…

Spring AI实战之二:Chat API基础知识大串讲(重要)

欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码)&#xff1a;https://github.com/zq2599/blog_demos Spring AI实战全系列链接 Spring AI实战之一&#xff1a;快速体验(OpenAI)Spring AI实战之二&#xff1a;Chat API基础知识大串讲(重要)SpringAIOllama三部曲…

JavaFX学习教程二

一、JavaFX 体系结构 JavaFX 场景图(Scene Graph)是构建 JavaFX 应用程序的起点&#xff0c;一种树状数据结构&#xff0c;用于排列&#xff08;和分组&#xff09;图形对象&#xff0c;以便于逻辑表示。 stage:舞台&#xff0c;操作系统窗口的 JavaFX 表示&#xff0c;是所有…

Qt for android 获取USB设备列表(一)Java方式 获取

简介 QtActivity 作为 Qt 应用程序的入口点&#xff0c;负责启动和配置 Qt 应用程序的信息&#xff0c; 后面我们继承 QtActivity 做自定义控制&#xff0c;了解一下 Activity 生命周期概念&#xff0c; 因为 QtActivity 继承自Android的activity&#xff0c;使用周期函数完成我…

VMare下载安装

一.下载 1.百度搜索BROADCOM官网 打开官网&#xff1a; https://www.broadcom.com/​ 2.点击右上角&#xff0c;进行账号注册&#xff0c;注册好后&#xff0c;进行登陆 3.注册好后&#xff0c;进入个人界面&#xff1a;https://support.broadcom.com/#. 按下图所示点击进…

VMware虚拟机中ubuntu使用记录(10)—— 如何在Ubuntu18.04中使用自己的单目摄像头运行ORB_SLAM3(亲测有效,踩坑记录)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、ORB_SLAM3源码编译二、ORB_SLAM3实时单目相机测试1. 查看摄像头的话题2. 运行测试 三. 运行测试可能的报错1. 报错一(1) 问题描述(2) 原因分析(3) 解决 2. …

大作业爬取手机数据,实现手机推荐系统以及朋友圈手机论坛

1、功能简介 &#xff08;1&#xff09;用户注册与用户登录 &#xff08;2&#xff09;手机搜索、手机比拼、手机个性化推荐 &#xff08;3&#xff09;点击搜索的手机图片会就用户行为&#xff0c;轮播展示用户行为&#xff0c;推荐点击次数靠前的手机 &#xff08;4&#xf…

基于springboot+vue的智慧外贸平台

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…