c++--智能指针(RAII)

2024-09-04 04:04
文章标签 c++ 指针 智能 raii

本文主要是介绍c++--智能指针(RAII),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

智能指针可以帮助我们管理动态空间,即自动释放动态空间。

---------------------------------------------------------------------------------------------------------------------------------

简单原理

事实上,智能指针的原理就是将指向动态空间的指针托管给一个自定义对象(把这个指针作为成员变量),然后在析构函数中将动态空间释放。这样就实现了动态空间的生命周期与对象的生命周期相同。如果要使用这个指针,我们只需要在类里面重载*,->等运算符或者写成员函数即可。

---------------------------------------------------------------------------------------------------------------------------------

智能指针的历史及主要困难

智能指针在c++98中就已经被引入,即现在c++库中的auto_ptr类模版。但是它在拷贝时有一个缺陷:

为什么报错呢?本来我们想要x和y共同管理一块动态空间,但是因为 auto_ptr 中的拷贝实际上是管理权转移,也就是说,当把x拷贝给y的时候,x就没有了该动态空间管理权(存放动态空间指针的成员变量被置为空了),所以x->year++实际上是对空指针操作,所以会报错。

为什么非要实现拷贝呢?不让它拷贝行吗?不行,我们总有一些情况是需要拷贝的,比如说:

push_back中就会拷贝(赋值拷贝),但是拷贝就会产生上述问题。还有传参等情况。

那么库里面为什么不能不把x置为空?这样也不行,因为会导致被管理的指针析构两次而报错。

这就是智能指针的困难

可以看出auto_ptr是不成熟的,但是如果在库里删除它会让一些已存在的使用了它程序无法运行。为了避免像auto_ptr这种尴尬的情况,后续委员会成员先在boost库(c++扩展库)写功能,如果好用的话才纳入c++标准库。boos库中就有新的好用的智能指针shared_ptr,unique_str(简单粗暴禁止拷贝,适用面不广)等,他们在c++11中被纳入标准库。

C++11和boost中智能指针的关系

1. C++ 98 中产生了第一个智能指针auto_ptr.

2. C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr.

3. C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。

4. C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost 的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的

---------------------------------------------------------------------------------------------------------------------------------

shared_ptr

这个是比较好用的一种智能指针。它通过引用计数的方式解决拷贝问题。

原理:对于管理同一份资源的智能指针,不在采用管理权转移的方式,让他们都有管理权,这样会导致析构两次,所以我们再让这些智能指针管理同一个计数器,每多一个智能指针管理这个资源,计数器加1,每析构一个智能指针,计数器减1。如果计数器成为0,说明这个智能指针是唯一的管理者,此时如果它析构,我们就把资源也释放了。

这种方式防止了析构多次以及同一份资源不能被多个智能指针管理的缺陷,俗称“最后一个离开房间的人关灯”

---------------------------------------------------------------------------------------------------------------------------------

接下来简单实现一下share_ptr

1.

这是类模版的头,T用来接收任意类型的资源

2.

_ptr指向存放动态资源的指针。

_count是指向计数器。管理同一个资源的智能指针中,这个指针指向的计数器相同。这个计数器是动态开辟的,在资源释放的时候将他也释放。我们给它缺省值为1。

_delete是删除器。智能指针指向的资源不光是一个动态开辟的变量(用delete释放),也有可能是FILE类型(用fclose释放),还有可能是动态数组类型,用delete[]释放,总之释放资源的方式是不同的,但我们写的模版要使用所有类型,所以我们在用智能指针管理资源的时候需要传入一个可调用对象(删除器),通过传入不同的删除器,实现资源的正确释放。至于为什么用包装器(function<void(T*)>),我们稍后再说。

3.

这是类模版的构造函数。

第一个函数是一个函数模版D是函数模版参数,由你传入的对象类型决定,T是类模版参数,由类决定。我们知道删除器是要释放资源,而释放资源是在析构函数内部,也就是说,我们要在析构函数内部使用D类型的对象。在一个函数传入,在另一个函数使用,如何做呢?他两共有的可以访问的是成员变量,所以我们可以用成员变量来接收这一对象,然后另一个函数就可以使用了。

那么问题又来了,这个成员变量应该是什么类型呢?是D,但问题是,D是函数模版参数,类模版没有这个模版参数,它不认识D,这时候就需要包装器了,它可以包装任何可调用对象,且只需要返回值和参数类型,参数类型就是指向资源的指针T*,返回值类型就是void

function<void(T*)> _delete = [](T* x)->void {delete x; };

而[](T* x)->void {delete x; }是一个lambda表达式,它作为删除器的缺省值,使得不传删除器的时候也可以有默认删除器使用。

至于为什么不直接把删除器设计成类模版参数,这是因为我们要模拟库的实现,哪种都可以的。

第二个函数是普通的构造函数

4.

这是析构函数。智能指针析构时,要先让count--,然后判断是否需要释放资源,也就是count是否为0(即这是最后一个管理这个资源的智能指针)。

而我把逻辑封装在release函数里。

5.

这是shared_ptr的关键,拷贝构造和赋值拷贝。

一但拷贝构造或者拷贝赋值,说明多了一个智能指针管理相同的资源,所以_count++。

对于拷贝复制,先得把该智能指针原来管理的资源清理(复用 release),然后接收新的资源。

6.

这是关于被管理资源的使用函数。const需要加上,以便const对象和非const对象都能调用这些函数,可以自己写去体会一下为什么要加。

---------------------------------------------------------------------------------------------------------------------------------

shared_ptr的循环引用

这是一个典型例子,下面是分析

这是shared_ptr的缺陷!

解决方法:

仿造库里面的weak_ptr,我们设计出自己的简化的weak_ptr(库里面要复杂许多倍)。

把节点改成这样:

用weak_ptr代替节点中的shared_ptr,shared_ptr可以赋值给weak_ptr(通过weak_ptr的赋值拷贝),weak_ptr管理一个资源不会增加计数器。这样,x->_next = y,y->_prev = x 后,count1,count2还是1而不是2,此时在走一遍上述过程,你会发现循环引用已经破除了。

---------------------------------------------------------------------------------------------------------------------------------

对了,weak_ptr可以通过lock函数恢复到shared_ptr,这里只是简单介绍智能指针,说的不全面,见谅

这篇关于c++--智能指针(RAII)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

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

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

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

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

C++读写word文档(.docx)DuckX库的使用详解

《C++读写word文档(.docx)DuckX库的使用详解》DuckX是C++库,用于创建/编辑.docx文件,支持读取文档、添加段落/片段、编辑表格,解决中文乱码需更改编码方案,进阶功能含文本替换... 目录一、基本用法1. 读取文档3. 添加段落4. 添加片段3. 编辑表格二、进阶用法1. 文本替换2

Rust 智能指针的使用详解

《Rust智能指针的使用详解》Rust智能指针是内存管理核心工具,本文就来详细的介绍一下Rust智能指针(Box、Rc、RefCell、Arc、Mutex、RwLock、Weak)的原理与使用场景,... 目录一、www.chinasem.cnRust 智能指针详解1、Box<T>:堆内存分配2、Rc<T>:

C++中处理文本数据char与string的终极对比指南

《C++中处理文本数据char与string的终极对比指南》在C++编程中char和string是两种用于处理字符数据的类型,但它们在使用方式和功能上有显著的不同,:本文主要介绍C++中处理文本数... 目录1. 基本定义与本质2. 内存管理3. 操作与功能4. 性能特点5. 使用场景6. 相互转换核心区别

C++右移运算符的一个小坑及解决

《C++右移运算符的一个小坑及解决》文章指出右移运算符处理负数时左侧补1导致死循环,与除法行为不同,强调需注意补码机制以正确统计二进制1的个数... 目录我遇到了这么一个www.chinasem.cn函数由此可以看到也很好理解总结我遇到了这么一个函数template<typename T>unsigned

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法

深入解析C++ 中std::map内存管理

《深入解析C++中std::map内存管理》文章详解C++std::map内存管理,指出clear()仅删除元素可能不释放底层内存,建议用swap()与空map交换以彻底释放,针对指针类型需手动de... 目录1️、基本清空std::map2️、使用 swap 彻底释放内存3️、map 中存储指针类型的对象

C++ STL-string类底层实现过程

《C++STL-string类底层实现过程》本文实现了一个简易的string类,涵盖动态数组存储、深拷贝机制、迭代器支持、容量调整、字符串修改、运算符重载等功能,模拟标准string核心特性,重点强... 目录实现框架一、默认成员函数1.默认构造函数2.构造函数3.拷贝构造函数(重点)4.赋值运算符重载函数

C++ vector越界问题的完整解决方案

《C++vector越界问题的完整解决方案》在C++开发中,std::vector作为最常用的动态数组容器,其便捷性与性能优势使其成为处理可变长度数据的首选,然而,数组越界访问始终是威胁程序稳定性的... 目录引言一、vector越界的底层原理与危害1.1 越界访问的本质原因1.2 越界访问的实际危害二、基