C++的智能指针 RAII

目录

产生原因

RAII思想

C++11的智能指针

智能指针的拷贝与赋值 

 shared_ptr的拷贝构造

shared_ptr的赋值重置

shared_ptr的其它成员函数

weak_ptr

定制删除器

简单实现


产生原因

产生原因:抛异常等原因导致的内存泄漏

int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}void Func()
{// 1、如果p1这里new 抛异常会如何?// 2、如果p2这里new 抛异常会如何?// 3、如果div调用这里又会抛异常会如何?int* p1 = new int;int* p2 = new int;cout << div() << endl;delete p1;delete p2;
}int main()
{try{Func();}catch (exception& e){cout << e.what() << endl;}return 0;
}

RAII思想

基本概念:智能指针(Smart Pointer)是C++中一种用于自动管理动态分配内存的对象,旨在防止内存泄漏和指针悬挂问题

核心理念:RAII,即一种利用对象生命周期来控制程序资源(如内存、网络连接等)的技术

工作原理:使用一份资源时,先用该资源构造一个智能指针,智能指针会保证在指向资源仍存在时始终有效(该资源的释放在智能指针的析构函数中),智能指针是由一个匿名对象构建得,为了能像指针一样使用,智能指针类中还会重载*和-> 

#include<iostream>
using namespace std;//智能指针类
template<class T>
class SmartPtr
{
public:// RAIISmartPtr(T* ptr)//接收一份资源的地址:_ptr(ptr)//_ptr指向一份资源{}~SmartPtr(){cout << "delete:" << _ptr << endl;delete _ptr;//智能指针对象释放时才释放_ptr指向的资源}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;
};int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}void Func()
{//创建一个int* 类型的智能指针SmartPtr<int> sp1(new int);SmartPtr<int> sp2(new int);*sp1 += 10;//像指针一样可以*SmartPtr<pair<string, int>> sp3(new pair<string, int>);//智能指针sp3指向得是一个pair类型的匿名对象sp3->first = "apple";sp3->second = 1;//等价于sp3.operator->()->second = 1;cout << div() << endl;
}int main()
{try{Func();}catch (const exception& e){cout << e.what() << endl;}return 0;
}

C++11的智能指针

基本概念:C++98时就已经提供了一个叫auto_ptr的智能指针,但使用该智能指针进行拷贝时,会出现管理权的转移,原对象的资源管理权交给了拷贝得到的新对象,这导致指向原对象的指针变为空指针,再次使用可能会报错;所以C++11在boost库的基础上引入了unique_ptr 和 shared_ptr等新的智能指针类C++11的智能指针均包含在<memory>头文件中,需要显示引用)

智能指针的拷贝与赋值 

1、unique_ptr 类不支持拷贝构造和赋值重载(通过只声明不定义实现,禁止了拷贝构造最好将赋值重载也禁掉,不禁掉则赋值重载是默认提供的会进行浅拷贝),适用于不需要拷贝的场景

2、shared_ptr 类有拷贝构造和赋值重载,并使用引用计数解决释放的问题(不使用静态成员进行计数是因为静态成员记录的是所有与被管理资源同类型的资源被使用的次数,即同类型的两个不同资源new出来的两个智能指针不会使得计数器++,而是重置变为1,当然我们设计得拷贝构造仍会使计数器++,但是new时可能会导计数器重置)

 shared_ptr的拷贝构造

基本概念:智能指针shared_ptr在创建时,除了有指向资源的指针ptr,还有该资源独属得计数器指针int * _count,初始时*(_count) = 1

// 构造函数
shared_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);
}

shared_ptr的赋值重置

基本概念:shared_ptr的赋值重载需要考虑无效赋值的情况

//赋值重置, sp1 = sp4
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()
{// 先进行计数器--,如果--后智能指针管理的资源的计数器变为0,就释放指向该资源的指针和计数器指针,然后再进行其它操作if (--(*_pcount) == 0){cout << "delete:" << _ptr << endl;delete _ptr;delete _pcount;}
}

shared_ptr的其它成员函数

基本概念:shared_ptr的析构函数并会直接将其管理的资源直接释放,还是要调用一个release函数进行--_count判断,即使是shared_ptr析构后,如果之前存在对该资源管理权的拷贝或者赋值,则用于记录该资源的计数器指针仍然不会被销毁,因为在拷贝或赋值时又有一个新的智能指针备份了该资源的信息(计数器指针指向的对象是new出来的int类型的对象除非主动释放,否则不会消失)

//析构函数
~shared_ptr()
{// 析构时,--计数,计数减到0,release();
}int use_count()
{return *_pcount;
}// 像指针一样
T& operator*()
{return *_ptr;
}T* operator->()
{return _ptr;
}T* get() const//为别人提供自己的指针,且该函数中不能修改自己的指针
{return _ptr;
}

shared_ptr的缺陷

基本概念:两个或多个对象通过 shared_ptr 相互引用,从而形成了一个循环。此时,即使所有外部 shared_ptr 都被销毁,由于循环引用中的 shared_ptr 仍然存在,引用计数永远不会降为零,导致这些对象无法被释放,造成内存泄漏

1、防止循环链表两个的结点因抛异常导致的无法释放,我们使用share_ptr管理这两个结点:

struct ListNode
{int _val;//④使得下面的n1->next = n2之类的操作不会因为双方类型不同导致无法互相赋值//struct ListNode* _next;//struct ListNode* _prev;//|//vstd::shared_ptr<ListNode> _next;std::shared_ptr<ListNode> _prev;ListNode(int val = 0):_val(val){}
};int main()
{//①ListNode* n1 = new ListNode(10);//ListNode* n2 = new ListNode(20);//|//vstd::shared_ptr<ListNode> n1 = ((new) ListNode(10);std::shared_ptr<ListNode> n2 = ((new) ListNode(20);//②中间可能出现抛异常    //|//v//不用担心抛异常了n1->next = n2;n2->prev = n1;//④delete n1;//delete n2;//此时不需要在这里delete了,在智能指针内部会deletereturn 0;
}

2、但是此时又会出现下面三种情况:

        两个结点互相用自己的智能指针管理对方时,管理两个结点的原始智能指针都析构后,记录两个结点的计数器均为1不会变为0,即两个结点均不会释放,此时出现内存泄漏问题,此时如果想要先释放右结点,那么就会出现以下的循环:

  • 这就是shared_ptr在特殊场景下的缺陷......

weak_ptr

 文档:weak_ptr - C++ Reference (cplusplus.com)

基本概念:为了解决shared_ptr在特殊场景下的缺陷,C++11还引入了weak_ptr,该智能指针不增加引用计数,不管理对象的生命周期

 注意事项:

1、weak_ptr不支持RAII(下面是便于理解的简化实现)

// 不支持RAII,不参与资源管理
template<class T>
class weak_ptr
{public:weak_ptr():_ptr(nullptr)//将传入的指针直接置空即可,不参与资源的管理{}weak_ptr(const shared_ptr<T>& sp){_ptr = sp.get();//获取shared_ptr的指针}weak_ptr<T>& operator=(const shared_ptr<T>& sp){_ptr = sp.get();return *this;}// 像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;
};

2、weak_ptr也有use_count函数,用于检测此时与weak_ptr指向同一资源的shared_ptr的个数,如果shared_ptr为0(该资源已经被释放了)则weak_ptr变为野指针,应及时置空

3、weak_ptr的expired函数用于检查weak_ptr是否过期,过期返回真,未过期返回假,相比use_count会更方便

总结:使用智能指针不一定会避免内存泄漏(上述结点时使用的纯shared_ptr),正确的使用智能指针才能避免内存泄漏(完善后的shared_ptr + weak_ptr)

定制删除器

基本概念: C++11并没有像boost库中的那样提供shared_array等用于管理和释放由[ ]创建的多个对象的智能指针,所以在使用shared_ptr管理和释放由[ ]开辟的多个对象时,会因为delete与new的方式不匹配而报错(shared_ptr的new和delete均为(),但如是[],释放时仍为()就可能导致出错)

std::shared_ptr<ListNode> p1(new ListNode(10));//正常情况
std::shared_ptr<ListNode> p1(new ListNode[10]);//有[]的情况,无法正确释放

因此C++11的智能指针还提供了接受自定义删除器的构造方式:

template <class U, class D> shared_ptr (U* p, D del);
  • D del自定义删除器,可以是函数指针、函数对象、Lambda 表达式等

1、自定义删除器是函数对象

template<class T>
struct DeleteArray
{void operator()(T* ptr){delete[] ptr;}
}std::shared_ptr<ListNode> p2(new ListNode[10],DeleteArray<ListNode>());

2、自定义删除器是Lambda表达式

std::shared_ptr<ListNode> p3(fopen("Test.cpp","r"),[](FILE* ptr){fclose(ptr); });

简单实现

template<class T>
class shared_ptr
{
public:template<class D>
shared_ptr(T* ptr, D del)//接收自定义删除器的构造函数:_ptr(ptr), _pcount(new int(1)), _del(del)//新定义一个成员用于存放删除器{}//删除函数
void release()
{// 说明最后一个管理对象析构了,可以释放资源了if (--(*_pcount) == 0){cout << "delete:" << _ptr << endl;//delete _ptr;_del(_ptr);//将执行资源的指针也给删除器一份,从而使删除器开始运行delete _pcount;}
}private:D _val;//shared_ptr的官方实现的模板中没有第二个模板参数,所以这种写法是错误的
}

//利用function将删除器的类型进行封装
//删除器的返回类型肯定是void,接收的参数类型肯定是T*
function<void(T*)> _del;
​
//为了防止使用无自定义删除器的构造函数时,没有del的传入导致的_del为空的情况,所以我们要提供一个默认的删除方式:
function<void(T*)> _del = [](T* ptr) {delete ptr; };

~over~

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

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

相关文章

手机usb共享网络电脑没反应的方法

适用于win10电脑&#xff0c;安卓手机上可以 开启usb网络共享选择&#xff0c;如果选择后一直跳&#xff0c;让重复选择usb选项的话&#xff0c;就开启 开发者模式&#xff0c;进到 开发者模式 里设置 默认usb 共享网络 选项 &#xff0c;就不会一直跳让你选。 1.先用数据线 连…

八大经典排序算法

前言 本片博客主要讲解一下八大排序算法的思想和排序的代码 &#x1f493; 个人主页&#xff1a;普通young man-CSDN博客 ⏩ 文章专栏&#xff1a;排序_普通young man的博客-CSDN博客 若有问题 评论区见&#x1f4dd; &#x1f389;欢迎大家点赞&#x1f44d;收藏⭐文章 目录 …

HTTP详细总结

概念 HyperText Transfer Protocol&#xff0c;超文本传输协议&#xff0c;规定了浏览器和服务器之间数据传输的规则。 特点 基于TCP协议: 面向连接&#xff0c;安全 TCP是一种面向连接的(建立连接之前是需要经过三次握手)、可靠的、基于字节流的传输层通信协议&#xff0c;在…

Linux管道与重定向

管道 是进程通信的方法之一&#xff0c;在Linux中用命令1|命令2的形式表示&#xff0c;将前一个命令的结果作为后续命令的参数进行输入&#xff0c;也有tee管道&#xff0c;可以进行多次筛选&#xff0c;即多次使用|过滤命令。 重定向 文件描述符FD Linux中输入输出分为三种…

C语言变量、指针的内存关系

1. type p ? 表示从内存地址p开始&#xff0c;开辟一段内存&#xff0c;内存大小为类型type规定的字节数&#xff0c;然后把等号右边的值写入到这段内存中。 因此&#xff0c;这块内存起点位置是p&#xff0c;结束是ptype字节数-1。 2. type* p ?表示从内存地址p开始&…

SpingBoot快速入门下

响应HttpServietResponse 介绍 将ResponseBody 加到Controller方法/类上 作用&#xff1a;将方法返回值直接响应&#xff0c;如果返回值是 实体对象/集合&#xff0c;将会自动转JSON格式响应 RestController Controller ResponseBody; 一般响应 统一响应 在实际开发中一般…

Python学习打卡:day11

day11 笔记来源于&#xff1a;黑马程序员python教程&#xff0c;8天python从入门到精通&#xff0c;学python看这套就够了 目录 day1183、自定义 Python 包创建包导入包方式1方式2方式3方式4 84、安装第三方包安装第三方包——pippip的网络优化 安装第三方包——PyCharm 85、…

代码随想录-Day36

452. 用最少数量的箭引爆气球 有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points &#xff0c;其中points[i] [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。 一支弓箭可以沿着 x 轴从不同点 完全垂…

基于WPF技术的换热站智能监控系统16--动态数据绑定

1、实现思路 1&#xff09;实时读取到的数据绑定到前台UI控件上&#xff0c;这个通过MVVM模式实现&#xff0c;同时注意实时读取必须通过任务task异步方式&#xff0c;这就需要读取PLC数据。 2&#xff09;UI控件的动作&#xff0c;如开或关水泵&#xff0c;必定能够将值写入…

Python | Leetcode Python题解之第169题多数元素

题目&#xff1a; 题解&#xff1a; class Solution:def majorityElement(self, nums: List[int]) -> int:count 0candidate Nonefor num in nums:if count 0:candidate numcount (1 if num candidate else -1)return candidate

Java | Leetcode Java题解之第171题Excel表列序号

题目&#xff1a; 题解&#xff1a; class Solution {public int titleToNumber(String columnTitle) {int number 0;int multiple 1;for (int i columnTitle.length() - 1; i > 0; i--) {int k columnTitle.charAt(i) - A 1;number k * multiple;multiple * 26;}ret…

《Windows API每日一练》5.2 按键消息

上一节中我们得知&#xff0c;Windows系统的按键消息有很多类型&#xff0c;大部分按键消息都是由Windows系统的默认窗口过程处理的&#xff0c;我们自己只需要处理少数几个按键消息。这一节我们将详细讲述Windows系统的所有按键消息及其处理方式。 本节必须掌握的知识点&…

wsl2平台鸿蒙全仓docker编译环境快速创建方法

文章目录 1 文章适用范围&#xff1a;2 WSL环境安装3 镜像迁移非C盘4 Docker环境准备4.1 docker用户组和用户创建4.2 Docker环境配置4.2.1 Ubuntu下安装docker工具4.2.2 鸿蒙Docker环境安装4.2.3 鸿蒙全仓代码拉取编译 5 鸿蒙全仓代码的更新策略6 参考文献7 FAQ7.1 缺头文件xcr…

每天写java到期末考试(6.21)--集合4--练习--6.20

练习1&#xff1a; 正常写集合 bool类 代码&#xff1a; import QM_Fx.Student;import java.util.ArrayList;public class test {public static void main(String[] args) {ArrayList<Student> listnew ArrayList<>();//2.创建学生对象Student s1new Student(&quo…

【面试干货】throw 和 throws 的区别

【面试干货】throw 和 throws 的区别 1、throw1.1 示例 2、throws2.1 示例 3、总结 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在Java中&#xff0c;throw和throws都与异常处理紧密相关&#xff0c;但它们在使用和含义上有明显的区别。…

【Linux】关于在华为云中开放了端口后仍然无法访问的问题

已在安全组中添加规则: 通过指令: netstat -nltp | head -2 && netstat -nltp | grep 8080 运行结果: 可以看到服务器确实处于监听状态了. 通过指令 telnet 公网ip port 也提示: "正在连接xxx.xx.xx.xxx...无法打开到主机的连接。 在端口 8080: 连接失败"…

[C++][数据结构][B-树][上]详细讲解

目录 0.常见的搜索结构1.B树概念2.B-树的插入分析1.流程分析2.插入过程总结 0.常见的搜索结构 种类数据格式时间复杂度顺序查找无要求 O ( N ) O(N) O(N)二分查找有序 O ( l o g 2 N ) O(log_2 N) O(log2​N)二叉搜索树无要求 O ( N ) O(N) O(N)二叉平衡树无要求 O ( l o g 2 …

Nvidia Isaac Sim搭建仿真环境 入门教程 2024(4)

Nvidia Isaac Sim 入门教程 2024 版权信息 Copyright 2023-2024 Herman YeAuromix. All rights reserved.This course and all of its associated content, including but not limited to text, images, videos, and any other materials, are protected by copyright law. …

怎么添加网页到桌面快捷方式?

推荐用过最棒的学习网站&#xff01;https://offernow.cn 添加网页到桌面快捷方式&#xff1f; 很简单&#xff0c;仅需要两步&#xff0c;接下来以chrome浏览器为例。 第一步 在想要保存的网页右上角点击设置。 第二步 保存并分享-创建快捷方式&#xff0c;保存到桌面即可…

【Unity】AssetBundle打包策略

【Unity】AssetBundle打包策略 在游戏开发过程中&#xff0c;AssetBundle(AB)打包策略的重要性不容忽视。游戏开发者往往手动设置游戏资源包名进行管理&#xff0c;难免会造成资源确实或导致冗余&#xff0c;因此对于AB包的打包流程来说&#xff0c;进行策略管理显得十分重要。…