C++进阶篇8---智能指针

一、引言

为什么需要智能指针?

在上一篇异常中,关于内存释放,我们提到过一个问题---当我们申请资源之后,由于异常的执行,代码可能直接跳过资源的释放语句到达catch,从而造成内存的泄露,对于这种情况,我们当时的解决方案是在抛出异常后,我们先对异常进行捕获,将资源释放,再将异常抛出,但这样做会使得代码变得很冗长,那有没有什么办法能让它自动释放内存资源呢?用智能指针

什么是智能指针?

说到自动释放资源,是不是有点熟悉,我们在学习创建类对象时,就知道当类对象的生命周期结束后,系统会自动调用它的析构函数,完成资源的释放,那么我将指针放入这样一个类对象中,将释放资源的工作交给析构函数,只要该对象生命周期结束,那么就释放该资源,如此就不用在关心资源的释放问题,只要函数栈帧销毁,即该对象被销毁,资源就会自动释放,这就叫智能指针。

智能指针的使用和原理

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

2.具有指针的行为,可以解引用,也可以通过->去访问所指空间中的内容

下面写一个简单的智能指针

namespace zxws
{template<class T>class smart_ptr{public:smart_ptr(T* ptr = nullptr):_ptr(ptr){}~smart_ptr(){cout << "delete _ptr" << endl;delete _ptr;_ptr = nullptr;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
}

但是上面这个智能指针有个严重的问题,一旦有两个对象同时指向同一个资源,那么析构函数就会被调用两次,即资源要被释放两次,会报错,如下


二、库中的智能指针

C++官方给出了3个智能指针

1.auto_ptr

auto_ptr:管理权转移的思想,即一个资源只能有一个指针能对它进行管理,其他的指向这一资源的指针均为空,实现如下

namespace zxws
{template<class T>class auto_ptr{public:auto_ptr(T* ptr = nullptr):_ptr(ptr){}//管理权限的转移auto_ptr(const auto_ptr& tmp):_ptr(tmp._ptr){tmp._ptr = nullptr;}auto_ptr& operator=(const auto_ptr& tmp){if (this != &tmp)//注意自己给自己赋值的情况不需要处理,否则会出问题{if (_ptr)//释放当前对象中资源delete _ptr;//管理权限转移_ptr = tmp._ptr;tmp._ptr = nullptr;}return *this;}~auto_ptr(){if (_ptr){delete _ptr;_ptr = nullptr;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
}

 2.unique_ptr

unique_ptr:简单粗暴的防拷贝,即一个指针只能被初始化一次,且只能用不同的资源初始化

实现如下

namespace zxws
{template<class T>class unique_ptr{public:unique_ptr(T* ptr = nullptr):_ptr(ptr){}//将拷贝构造和赋值重载直接ban掉unique_ptr(const unique_ptr& tmp) = delete;unique_ptr& operator=(const unique_ptr& tmp) = delete;~unique_ptr(){if (_ptr){delete _ptr;_ptr = nullptr;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
}

3.shared_ptr

shared_ptr是通过引用计数的方式来实现多个shared_ptr对象之间共享资源

具体原理如下

1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享
2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源
4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
实现如下
namespace zxws
{	template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr):_ptr(ptr),_pcount(new int(1)){}shared_ptr(const shared_ptr& tmp):_ptr(tmp._ptr),_pcount(tmp._pcount){(*_pcount)++;}shared_ptr& operator=(const shared_ptr& tmp){//这里注意自己给自己赋值的情况!!!//当引用计数为1时,就会出现将资源释放后,在赋值的尴尬情况//用this!=&tmp也没用,可能出现两个不同对象指向同一块资源的情况//所以用资源的地址来判断最准确if (_ptr != tmp._ptr){release();_ptr = tmp._ptr;_pcount = tmp._pcount;(*_pcount)++;}return *this;}void release(){if (--(*_pcount)==0){delete _ptr;delete _pcount;_pcount = nullptr;_ptr = nullptr;}}~shared_ptr(){release();}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}int use_count() const{return *_pcount;}private:T* _ptr;int* _pcount;};
}

那么引用计数,为什么要用指针开辟的空间,而不是成员变量或者静态成员变量?

1、如果是成员变量,那么每一个shared_ptr对象都会有一个_pcount

2、如果是静态成员变量,那么_pcount将属于一个类

两者都不能满足我们的需求

关于shared_ptr还存在一个循环引用的问题,场景如下

当我们将循环链表的两个结点连接起来的时候,就不会释放结点空间,但是只要有一条边没链接就都能释放,为什么???

而只连接一条边,这个闭环就不复存在,所以两个结点都能释放,那如何解决这种情况?

针对这种情况,C++官方设计出了weak_ptr来和shared_ptr搭配使用,也就是说weak_ptr不增加shared_ptr的引用计数,且不参与资源的释放

实现如下

namespace zxws
{	template<class T>class weak_ptr{public:weak_ptr():_ptr(nullptr){}weak_ptr(const shared_ptr<T>& tmp):_ptr(tmp.get()){}weak_ptr& operator=(const shared_ptr<T>& tmp){_ptr = tmp.get();return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
}

(上面三个智能指针的模拟实现是被简化过的,功能不全,但是核心就是这些)

其中auto_ptr这个智能指针基本不用

上面写的三个智能指针还有一个缺陷,就是释放资源的delete写死了,如果我们开的是一个数组,就需要用delete[],否则资源的释放就会出现问题,所以就需要我们定制化它们的释放资源的方式,根据前面的知识,我们可以给它传一个释放资源的仿函数,如下

template<class T>
struct Destroy {void operator()(T*_ptr){delete[] _ptr;}
};
template<class T, class D>
class shared_ptr
{//....
};
shared_ptr<int, Destroy<int>>p;

但是库中只写了一个模板参数

我们如果想实现和库中一样的效果,该怎么写?

既然传模板参数不行,我们只能传函数对象了,用function包装器和lambda表达式实现如下

namespace zxws
{template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr):_ptr(ptr),_pcount(new int(1)){}shared_ptr(T* ptr,function<void(T*)> del):_ptr(ptr),_pcount(new int(1)),_del(del){}shared_ptr(const shared_ptr& tmp):_ptr(tmp._ptr),_pcount(tmp._pcount),_del(tmp._del){(*_pcount)++;}shared_ptr& operator=(const shared_ptr& tmp){//这里注意自己给自己赋值的情况!!!//当引用计数为1时,就会出现将资源释放后,在赋值的尴尬情况//用this!=&tmp也没用,可能出现两个不同对象指向同一块资源的情况//所以用资源的地址来判断最准确if (_ptr != tmp._ptr){release();_ptr = tmp._ptr;_pcount = tmp._pcount;_del = tmp._del;(*_pcount)++;}return *this;}void release(){if (--(*_pcount)==0){_del(_ptr);delete _pcount;_ptr = nullptr;_pcount = nullptr;}}~shared_ptr(){release();}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}int use_count() const{return *_pcount;}private:T* _ptr;int* _pcount;function<void(T*)>_del = [](T* ptr) {delete ptr; };};
}

其他几个智能指针写法类似,就不写了。

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

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

相关文章

C# Winform 日志系统

目录 一、效果 1.刷新日志效果 2.单独日志的分类 3.保存日志的样式 二、概述 三、日志系统API 1.字段 Debug.IsScrolling Debug.Version Debug.LogMaxLen Debug.LogTitle Debug.IsConsoleShowLog 2.方法 Debug.Log(string) Debug.Log(string, params object[]) …

FFmpeg抽取视频h264数据重定向

根据视频重定向技术解析中的 截获解码视频流的思路&#xff0c;首先需要解决如何输出视频码流的问题。 目前只针对h264码流进行获取&#xff0c;步骤如下&#xff1a; 打开mp4文件并创建一个空文件用于存储H264数据 提取一路视频流资源 循环读取流中所有的包(AVPacket),为…

线程安全3--wait和notify

文章目录 wait and notify&#xff08;等待通知机制notify补充 wait and notify&#xff08;等待通知机制 引入wait notify就是为了能够从应用层面上&#xff0c;干预到多个不同线程代码的执行顺序&#xff0c;这里说的干预&#xff0c;不是影响系统的线程调度策略&#xff08…

uni-app应用设置 可以根据手机屏幕旋转进行 (横/竖) 屏切换

首先 我们打开项目的 manifest.json 在左侧导航栏中找到 源码视图 然后找到 app-plus 配置 在下面加上 "orientation": [//竖屏正方向"portrait-primary",//竖屏反方向"portrait-secondary",//横屏正方向"landscape-primary",//横屏…

IDEA启动应用时报错:错误: 找不到或无法加载主类 @C:\Users\xxx\AppData\Local\Temp\idea_arg_filexxx

IDEA启动应用时报错&#xff0c;详细错误消息如下&#xff1a; C:\devel\jdk1.8.0_201\bin\java.exe -agentlib:jdwptransportdt_socket,address127.0.0.1:65267,suspendy,servern -XX:TieredStopAtLevel1 -noverify -Dspring.output.ansi.enabledalways -Dcom.sun.management…

基于以太坊的智能合约开发Solidity(事件日志篇)

//声明版本号&#xff08;程序中的版本号要和编译器版本号一致&#xff09; pragma solidity ^0.5.17; //合约 contract EventTest {//状态变量uint public Variable;//构造函数constructor() public{Variable 100;}event ValueChanged(uint newValue); //事件声明event Log(…

class064 Dijkstra算法、分层图最短路【算法】

class064 Dijkstra算法、分层图最短路【算法】 算法讲解064【必备】Dijkstra算法、分层图最短路 code1 743. 网络延迟时间 // Dijkstra算法模版&#xff08;Leetcode&#xff09; // 网络延迟时间 // 有 n 个网络节点&#xff0c;标记为 1 到 n // 给你一个列表 times&…

法律服务网站建设效果如何

律师事务所及法律知识咨询机构等往往是众多人群需求的服务&#xff0c;服务多样化及内容多元化&#xff0c;市场中也有大量品牌&#xff0c;在实际消费服务中大多以本地事务所为主&#xff0c;而线上咨询服务则一般没有区域限制&#xff0c;同行增多及人们知识获取渠道增加&…

C++-引用和指针区别

文章目录 1.变量的组成2.指针2.1 定义2.2 使用指针操作变量2.3 为什么使用指针 3.引用3.1 定义3.2 引用注意事项 4.引用和指针的区别 1.变量的组成 变量的组成&#xff1a;变量地址&#xff0c;变量名&#xff0c;变量值 例&#xff1a; int i 12;2.指针 2.1 定义 指针用于存…

如何为游戏角色3D模型设置纹理贴图

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 当谈到游戏角色的3D模型风格时&#xff0c;有几种不同的风格&#xf…

Mybatis中的查询操作

单表查询 单表查询在《初始Mybatis》中已经介绍过&#xff0c;这里就不在介绍了。咱们这里只说单表查询中的“like查询”。like查询单独使用#{}报错 <select id"selectByKeyword" resultType"com.example.demo.entity.Userinfo">select * from use…

计网Lesson8 - NAT技术与链路层概述

文章目录 NAT 技术1. 因特网的接入方式2. 公网和私网3. NAT 技术 链路层1. 数据链路层概述2. 数据链路层的三个问题2.1 封装成帧2.2 透明传输2.3 差错检测 NAT 技术 1. 因特网的接入方式 光猫将电信号转换为数字信号发送给路由器 光纤入户 光纤传递的就是数字信号&#xff0c…

python+pytest接口自动化(12)-自动化用例编写思路 (使用pytest编写一个测试脚本)

经过之前的学习铺垫&#xff0c;我们尝试着利用pytest框架编写一条接口自动化测试用例&#xff0c;来厘清接口自动化用例编写的思路。 我们在百度搜索天气查询&#xff0c;会出现如下图所示结果&#xff1a; 接下来&#xff0c;我们以该天气查询接口为例&#xff0c;编写接口测…

每日一练2023.12.9—— 矩阵A乘以B【PTA】

题目链接&#xff1a;L1-048 矩阵A乘以B 题目要求&#xff1a; 给定两个矩阵A和B&#xff0c;要求你计算它们的乘积矩阵AB。需要注意的是&#xff0c;只有规模匹配的矩阵才可以相乘。即若A有Ra​行、Ca​列&#xff0c;B有Rb​行、Cb​列&#xff0c;则只有Ca​与Rb​相等时&a…

Linux Shell 基础命令

Linux 是一个开源的操作系统&#xff0c;其命令行界面是它的重要组成部分。在这个界面下&#xff0c;Shell 是一个能够与操作系统进行交互的工具。Shell 是一种程序&#xff0c;它能够接收用户输入的命令&#xff0c;并将这些命令发送到操作系统中进行处理。 在 Linux 中&…

VINS-MONO代码解读5----vins_estimator(marginalization部分)

文章目录 0. 前言1.1 Marginalization Pipiline 1. marg factor构建1.1 变量及维度理解1.2 IMUFactor1.3 ProjectionTdFactor(ProjectionFactor)1.4 MarginalizationFactor( e p e_p ep​推导更新&#xff0c;FEJ解决的问题)1.4.1 先验残差的更新1.4.2 先验Jacobian的更新 2. R…

windows install git

refer: https://developers.weixin.qq.com/miniprogram/dev/devtools/wechatvcs.html https://blog.csdn.net/weixin_40228200/article/details/128451324 在使用小程序的时候&#xff0c;需要初始化项目&#xff0c;需要注册Git账号 1.在本地确认cmd没有安装Git,进入Git官网…

docker:安装mysql以及最佳实践

文章目录 1、拉取镜像2、运行容器3、进入容器方式一方式二方式三容器进入后连接mysql和在宿主机连接mysql的区别 持久化数据持久化数据最佳实践 1、拉取镜像 docker pull mysql2、运行容器 docker run -d -p 3307:3306 --name mysql-container -e MYSQL_ROOT_PASSWORD123456 …

Botton进一步了解(点击事件)

点击事件和长按事件 监听器&#xff1a;专门监听控件的动作行为。只有控件发生了指定的动作&#xff0c;监听器才会触发开关区执行对应的代码逻辑。按钮控件有两种常用的监听器&#xff1a; 点击监听器&#xff1a;通过setOnClickListener方法设置。按钮被按住少于500ms时会触…

2023济南大学acm新生赛题解

通过答题情况的难度系数&#xff1a; 签到&#xff1a;ACI 铜牌题&#xff1a;BG 银牌题&#xff1a;EF 金牌题&#xff1a;DHJKO 赛中暂未有人通过&#xff1a;LMNP A - AB Problem 直接根据公式计算就行。 #include<stdio.h> int main(){int a,b;scanf("%…