【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

相关文章

C++中unordered_set哈希集合的实现

《C++中unordered_set哈希集合的实现》std::unordered_set是C++标准库中的无序关联容器,基于哈希表实现,具有元素唯一性和无序性特点,本文就来详细的介绍一下unorder... 目录一、概述二、头文件与命名空间三、常用方法与示例1. 构造与析构2. 迭代器与遍历3. 容量相关4

C++中悬垂引用(Dangling Reference) 的实现

《C++中悬垂引用(DanglingReference)的实现》C++中的悬垂引用指引用绑定的对象被销毁后引用仍存在的情况,会导致访问无效内存,下面就来详细的介绍一下产生的原因以及如何避免,感兴趣... 目录悬垂引用的产生原因1. 引用绑定到局部变量,变量超出作用域后销毁2. 引用绑定到动态分配的对象,对象

SpringBoot基于注解实现数据库字段回填的完整方案

《SpringBoot基于注解实现数据库字段回填的完整方案》这篇文章主要为大家详细介绍了SpringBoot如何基于注解实现数据库字段回填的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解... 目录数据库表pom.XMLRelationFieldRelationFieldMapping基础的一些代

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java AOP面向切面编程的概念和实现方式

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概... 目录一、AOP 是什么?二、AOP 的核心概念与实现方式核心概念实现方式三、Spring AOP 的关

Python实现字典转字符串的五种方法

《Python实现字典转字符串的五种方法》本文介绍了在Python中如何将字典数据结构转换为字符串格式的多种方法,首先可以通过内置的str()函数进行简单转换;其次利用ison.dumps()函数能够... 目录1、使用json模块的dumps方法:2、使用str方法:3、使用循环和字符串拼接:4、使用字符

Linux下利用select实现串口数据读取过程

《Linux下利用select实现串口数据读取过程》文章介绍Linux中使用select、poll或epoll实现串口数据读取,通过I/O多路复用机制在数据到达时触发读取,避免持续轮询,示例代码展示设... 目录示例代码(使用select实现)代码解释总结在 linux 系统里,我们可以借助 select、

Linux挂载linux/Windows共享目录实现方式

《Linux挂载linux/Windows共享目录实现方式》:本文主要介绍Linux挂载linux/Windows共享目录实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录文件共享协议linux环境作为服务端(NFS)在服务器端安装 NFS创建要共享的目录修改 NFS 配

通过React实现页面的无限滚动效果

《通过React实现页面的无限滚动效果》今天我们来聊聊无限滚动这个现代Web开发中不可或缺的技术,无论你是刷微博、逛知乎还是看脚本,无限滚动都已经渗透到我们日常的浏览体验中,那么,如何优雅地实现它呢?... 目录1. 早期的解决方案2. 交叉观察者:IntersectionObserver2.1 Inter

Spring Gateway动态路由实现方案

《SpringGateway动态路由实现方案》本文主要介绍了SpringGateway动态路由实现方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随... 目录前沿何为路由RouteDefinitionRouteLocator工作流程动态路由实现尾巴前沿S