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.hqwc.cn/news/264227.html

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

相关文章

Kafka 最佳实践:构建可靠、高性能的分布式消息系统

Apache Kafka 是一个强大的分布式消息系统&#xff0c;被广泛应用于实时数据流处理和事件驱动架构。为了充分发挥 Kafka 的优势&#xff0c;需要遵循一些最佳实践&#xff0c;确保系统在高负载下稳定运行&#xff0c;数据可靠传递。本文将深入探讨 Kafka 的一些最佳实践&#x…

zxjy008- 项目集成Swagger

Swagger可以生成在线文档&#xff0c;还可以进行接口测试。 1、创建common模块(maven类型) 为了让所有的微服务子子模块都可以使用&#xff0c;可以在guli_parent父工程下面创建公共模块 1.1 在guli_parent父工程下面创建公共模块 配置&#xff1a; groupId&#xff1a;com…

[软件工具]文本去重含有重复的全部删除不是保留一个重复的方法

文本去重含有重复的全部删除不是保留一个重复的方法 第一步&#xff1a;首先打开软件 第二步&#xff1a;设置好保存目录后&#xff0c;将文件夹拖拽到列表&#xff0c;软件会自动识别导入txt 第三步&#xff1a;点击开始处理&#xff0c;即可完成任务 本软件支持批量处理&a…

面试官:性能测试瓶颈调优你是真的会吗?

引言&#xff1a;性能瓶颈调优 在实际的性能测试中&#xff0c;会遇到各种各样的问题&#xff0c;比如 TPS 压不上去等&#xff0c;导致这种现象的原因有很多&#xff0c;测试人员应配合开发人员进行分析&#xff0c;尽快找出瓶颈所在。 理想的性能测试指标结果可能不是很高&…

三个臭皮匠(ctr,nerdctl,crictl)顶一个诸葛亮(docker)

文章目录 containerd简介 nerdctl简介安装精简 Minimal 安装完整Full 安装启动服务 命令参数容器运行容器列出容器详情容器日志容器进入容器停止容器删除镜像列表镜像拉取镜像标签镜像导出镜像导入镜像删除镜像构建配置tab键配置加速配置仓库http方式https方式 ctr简介命令参数…

AWVS 工具学习

AWVS 1.1 工具的下载路径1.2 工具的安装流程1.3 工具的详细使用1.3.1 功能模块 AWVS是Acunetix Web Vulnerability Scanner的缩写&#xff0c;是一种广泛使用的自动化网络应用程序安全扫描工具。它的主要功能是检测和识别Web应用程序中的漏洞和安全风险&#xff0c;以帮助组织…

集合的基本内容以及迭代器的介绍

在我们java中我们存储数据的方式&#xff0c;在我们之前学习中数组是可以存储我们的数据的&#xff0c;但是数组存储数据有一些弊端&#xff0c;灵活性不强&#xff0c;存储数据类型有限&#xff0c;灵活性不强这一点主要是体现在两个方面&#xff1a;一方面数组的长度是自定义…

基于Java Swing泡泡龙游戏(Java毕业设计)

大家好&#xff0c;我是DeBug&#xff0c;很高兴你能来阅读&#xff01;作为一名热爱编程的程序员&#xff0c;我希望通过这些教学笔记与大家分享我的编程经验和知识。在这里&#xff0c;我将会结合实际项目经验&#xff0c;分享编程技巧、最佳实践以及解决问题的方法。无论你是…

01_W5500简介

目录 W5500简介&#xff1a; 芯片特点: 全硬件TCPIP协议栈: 引脚分布&#xff1a; W5500简介&#xff1a; W5500是一款高性价比的以太网芯片&#xff0c;其全球独一无二的全硬件TCPIP协议栈专利技术&#xff0c;解决了嵌入式以太网的接入问题&#xff0c;简单易用&#xff…

javaSwing酒店管理系统

一、 使用方法&#xff1a; 在使用前&#xff0c;需要到druid.properties 配置文件中&#xff0c;修改自己对应于自己数据库的属性&#xff1b;如用户名&#xff0c;密码等 driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql:///hotel?useUnicodetrue&characterEn…

高级Linux监控堡垒机学习指南

高级Linux监控堡垒机学习指南 在现代复杂的网络环境中&#xff0c;安全性和监控是系统管理的核心关注点。Linux监控堡垒机作为一种安全管理工具&#xff0c;不仅可以追踪系统活动&#xff0c;还能提供对服务器和网络资源的高级监控。本文将深入探讨高级Linux监控堡垒机的学习内…

Jenkins参数化构建及代码发布

如何使用gitlab--web端可以观看此篇教程 https://blog.csdn.net/m0_59933574/article/details/134528050?spm1001.2014.3001.5502https://blog.csdn.net/m0_59933574/article/details/134528050?spm1001.2014.3001.5502 整体思路 依赖环境及工具 Git Centos7及以上 Gitla…