【C++】详细版 RAII技术的应用之智能指针(智能指针发展历程和简单模拟实现介绍)

本文主要是介绍【C++】详细版 RAII技术的应用之智能指针(智能指针发展历程和简单模拟实现介绍),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 

目录

 

前言

一、智能指针有什么用?

二、什么是RAII(智能指针的底层思想)?

       三、智能指针的发展历程以及模拟实现

1.auto_ptr(C++98)

2.unique_ptr(C++11)

3.shared_ptr(C++11)


前言

C++中的智能指针是一种管理内存的工具,它可以自动地跟踪和管理所指向的内存块。智能指针通常用于替代手动管理内存的机制,避免内存泄漏和野指针等问题。


一、智能指针有什么用?

下面我们来看一种场景:

#include <iostream>
using namespace std;
double Division(int x, double y)
{cin >> x >> y;if (0 == y)throw invalid_argument("除数为0无法计算");return x / y;
}
void func()
{pair<int, string>* p1 = new pair<int, string>(7, "CSDN");int x;double y;cin >> x >> y;cout << Division(x, y) << endl;delete p1;p1 = nullptr;
}
int main()
{try{func();}catch (exception& e){cout << e.what() << endl;}return 0;
}

从上面代码可以分析出:如果Dvision函数中抛异常的话,那么p1指向的空间内存就无法释放,造成内存泄露。因此此时就需要一个智能指针对p1指向的空间内存进行自动释放。因此我们可以这样做:利用对象的生命周期来控制手动开辟的内存资源。下面我们来简单实现一下着种方法:

#include <iostream>
using namespace std;
class A
{
public:A(pair<int, string>* ptr):_ptr(ptr){}~A(){cout << "delete" << endl;}
private:pair<int, std:string>* _ptr;
};
double Division(int x, double y)
{cin >> x >> y;if (0 == y)throw invalid_argument("除数为0无法计算");return x / y;
}
void func()
{pair<int, string>* p1 = new pair<int, string>(7, "CSDN");A a(p1);int x;double y;cin >> x >> y;cout << Division(x, y) << endl;delete p1;p1 = nullptr;
}
int main()
{try{func();}catch (exception& e){cout << e.what() << endl;}return 0;
}

1.当Division函数中不抛异常的情况,代码运行结果如下:

0928870634c94246a8fe5975d815164a.png

2.当Division函数中抛异常的情况,代码运行结果如下:

6c932c81f1c245088e28e582341fdc7c.png

 由此可见,将p1指向的资源托管给对象a控制是利于其资源释放的,p1指向的资源会随着对象a的销毁而销毁。

二、什么是RAII(智能指针的底层思想)?

智能指针是RAII技术的应用。

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

三、智能指针的发展历程以及模拟实现

智能指针的大体是有三个阶段的发展:第一阶段C++98的auto_ptr;第二阶段C++11的unique_ptr;第三阶段C++11中的shared_ptr。通过不断创新与努力C++11最终发布了shared_ptr,这也是智能指针的最终版本,是最优的。

我们分别来模拟实现一下这几种智能指针,以及对它们做出分析。

在此之前我们必须要明白其实智能指针是一个类对象,该类封装了所需要管理的资源,以及内部实现了具有指针功能的运算符重载成员函数(operator*  operator->)。

1.auto_ptr(C++98)

auto_ptr 的实现原理:管理权转移的思想,下面简化模拟实现了一份 keke::auto_ptr 来了解它的原
#include <iostream>
using namespace std;
namespace keke
{template<class T >class auto_ptr{public:auto_ptr(T* ptr):_ptr(ptr){}auto_ptr(auto_ptr& ap):_ptr(ap._ptr){//资源管理权转移ap._ptr = nullptr;}auto_ptr<T>& operator=(auto_ptr& ap){//检查是否为自己给自己赋值,如果是的话那么不进行赋值if (_ptr != ap._ptr){//释放被赋值对象里的资源if (_ptr)delete _ptr;//将对象ap的资源转移给被赋值对象_ptr = ap._ptr;ap._ptr = nullptr;}return *this;}~auto_ptr(){if (_ptr)delete _ptr;}//要像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
}
int main()
{keke::auto_ptr<int> ap1(new int(5));keke::auto_ptr<int> ap2(ap1);++(*ap1);//由于ap1将资源权限转移给了ap2,则ap1_ptr==nullptr//导致读取数据错误!++(*ap2);return 0;
}

b3b48b5b8afd4d61b54b385e2a675c56.png

基于上面的问题,auto_ptr是不建议被使用的。

2.unique_ptr(C++11)

unique_ptr的实现原理: 简单粗暴的防拷贝,下面简化模拟实现了一份UniquePtr 来了解它的原

unique_ptr是在auto_ptr的基础上改进的。只是将auto_ptr模拟实现中的拷贝构造函数和赋值运算符重载访问权限改为private,或者是在两个默认成员函数后加=delete。

template<class T >class unique_ptr{public:unique_ptr(T* ptr):_ptr(ptr){}unique_ptr(unique_ptr& up) = delete;unique_ptr<T>& operator=(unique_ptr& up) = delete;~unique_ptr(){if (_ptr)delete _ptr;}//要像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};

智能指针unique_ptr对于不需要拷贝的场景适用,但是需要拷贝的场景则不能使用。

3.shared_ptr

C++11 中开始提供更靠谱的并且支持拷贝的 shared_ptr。
shared_ptr 的原理: 是通过引用计数的方式来实现多个shared_ptr对象之间共享资源
举一个例子:放学后最后一个走出教室的同学需要把教室的门上锁。
1. shared_ptr在其内部, 给每个资源都维护了着一份计数,用来记录该份资源被几个对象共
2. 在 对象被销毁时 ( 也就是析构函数调用 ),就说明自己不使用该资源了,对象的引用计数减
一。
3. 如果引用计数是 0,就说明自己是最后一个使用该资源的对象, 必须释放该资源
4. 如果不是 0,就说明除了自己还有其他对象在使用该份资源, 不能释放该资源,否则其他对
象就成野指针了。
#include <iostream>
using namespace std;
namespace keke
{template<class T >class shared_ptr{public:shared_ptr(T* ptr = nullptr):_ptr(ptr),_pCount(new int(1)){}shared_ptr(shared_ptr& sp):_ptr(sp._ptr),_pCount(sp._pCount){++(*_pCount);}shared_ptr<T>& operator=(shared_ptr& sp){if (_ptr != sp._ptr)//检查是否为自己给自己赋值Release();_ptr = sp._ptr;_pCount = sp._pCount;++(*_pCount);return *this;}void Release(){if (0 == --(*_pCount) && _ptr){cout << "delete" << endl;delete _ptr;delete _pCount;}}~shared_ptr(){Release();}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}int use_count(){return *_pCount;}private:T* _ptr;int* _pCount;};
}
int main()
{keke::shared_ptr<string> sp1(new string("CSDN"));keke::shared_ptr<string> sp2 = sp1;keke::shared_ptr<string> sp3(sp1);keke::shared_ptr<pair<int, string>> sp4(new pair<int, string>(5, "CSDN"));keke::shared_ptr<pair<int, string>> sp5(sp4);return 0;
}
运行结果如下:
39d14850788344428b9c4db955af0722.png

 shared_ptr智能指针采用计数,让最后一个释放的对象释放资源,虽然复杂一下,但是非常完美!shared_ptr也是目前主要使用的智能指针。

 

 

这篇关于【C++】详细版 RAII技术的应用之智能指针(智能指针发展历程和简单模拟实现介绍)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/977963

相关文章

QT Creator配置Kit的实现示例

《QTCreator配置Kit的实现示例》本文主要介绍了使用Qt5.12.12与VS2022时,因MSVC编译器版本不匹配及WindowsSDK缺失导致配置错误的问题解决,感兴趣的可以了解一下... 目录0、背景:qt5.12.12+vs2022一、症状:二、原因:(可以跳过,直奔后面的解决方法)三、解决方

深入浅出Spring中的@Autowired自动注入的工作原理及实践应用

《深入浅出Spring中的@Autowired自动注入的工作原理及实践应用》在Spring框架的学习旅程中,@Autowired无疑是一个高频出现却又让初学者头疼的注解,它看似简单,却蕴含着Sprin... 目录深入浅出Spring中的@Autowired:自动注入的奥秘什么是依赖注入?@Autowired

MySQL中On duplicate key update的实现示例

《MySQL中Onduplicatekeyupdate的实现示例》ONDUPLICATEKEYUPDATE是一种MySQL的语法,它在插入新数据时,如果遇到唯一键冲突,则会执行更新操作,而不是抛... 目录1/ ON DUPLICATE KEY UPDATE的简介2/ ON DUPLICATE KEY UP

Python中Json和其他类型相互转换的实现示例

《Python中Json和其他类型相互转换的实现示例》本文介绍了在Python中使用json模块实现json数据与dict、object之间的高效转换,包括loads(),load(),dumps()... 项目中经常会用到json格式转为object对象、dict字典格式等。在此做个记录,方便后续用到该方

JWT + 拦截器实现无状态登录系统

《JWT+拦截器实现无状态登录系统》JWT(JSONWebToken)提供了一种无状态的解决方案:用户登录后,服务器返回一个Token,后续请求携带该Token即可完成身份验证,无需服务器存储会话... 目录✅ 引言 一、JWT 是什么? 二、技术选型 三、项目结构 四、核心代码实现4.1 添加依赖(pom

SpringBoot路径映射配置的实现步骤

《SpringBoot路径映射配置的实现步骤》本文介绍了如何在SpringBoot项目中配置路径映射,使得除static目录外的资源可被访问,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一... 目录SpringBoot路径映射补:springboot 配置虚拟路径映射 @RequestMapp

Python与MySQL实现数据库实时同步的详细步骤

《Python与MySQL实现数据库实时同步的详细步骤》在日常开发中,数据同步是一项常见的需求,本篇文章将使用Python和MySQL来实现数据库实时同步,我们将围绕数据变更捕获、数据处理和数据写入这... 目录前言摘要概述:数据同步方案1. 基本思路2. mysql Binlog 简介实现步骤与代码示例1

Redis实现高效内存管理的示例代码

《Redis实现高效内存管理的示例代码》Redis内存管理是其核心功能之一,为了高效地利用内存,Redis采用了多种技术和策略,如优化的数据结构、内存分配策略、内存回收、数据压缩等,下面就来详细的介绍... 目录1. 内存分配策略jemalloc 的使用2. 数据压缩和编码ziplist示例代码3. 优化的

基于C#实现PDF转图片的详细教程

《基于C#实现PDF转图片的详细教程》在数字化办公场景中,PDF文件的可视化处理需求日益增长,本文将围绕Spire.PDFfor.NET这一工具,详解如何通过C#将PDF转换为JPG、PNG等主流图片... 目录引言一、组件部署二、快速入门:PDF 转图片的核心 C# 代码三、分辨率设置 - 清晰度的决定因

Java Kafka消费者实现过程

《JavaKafka消费者实现过程》Kafka消费者通过KafkaConsumer类实现,核心机制包括偏移量管理、消费者组协调、批量拉取消息及多线程处理,手动提交offset确保数据可靠性,自动提交... 目录基础KafkaConsumer类分析关键代码与核心算法2.1 订阅与分区分配2.2 拉取消息2.3