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++ 中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 越界访问的实际危害二、基

c++日志库log4cplus快速入门小结

《c++日志库log4cplus快速入门小结》文章浏览阅读1.1w次,点赞9次,收藏44次。本文介绍Log4cplus,一种适用于C++的线程安全日志记录API,提供灵活的日志管理和配置控制。文章涵盖... 目录简介日志等级配置文件使用关于初始化使用示例总结参考资料简介log4j 用于Java,log4c

C++归并排序代码实现示例代码

《C++归并排序代码实现示例代码》归并排序将待排序数组分成两个子数组,分别对这两个子数组进行排序,然后将排序好的子数组合并,得到排序后的数组,:本文主要介绍C++归并排序代码实现的相关资料,需要的... 目录1 算法核心思想2 代码实现3 算法时间复杂度1 算法核心思想归并排序是一种高效的排序方式,需要用

使用Python构建智能BAT文件生成器的完美解决方案

《使用Python构建智能BAT文件生成器的完美解决方案》这篇文章主要为大家详细介绍了如何使用wxPython构建一个智能的BAT文件生成器,它不仅能够为Python脚本生成启动脚本,还提供了完整的文... 目录引言运行效果图项目背景与需求分析核心需求技术选型核心功能实现1. 数据库设计2. 界面布局设计3

C++11范围for初始化列表auto decltype详解

《C++11范围for初始化列表autodecltype详解》C++11引入auto类型推导、decltype类型推断、统一列表初始化、范围for循环及智能指针,提升代码简洁性、类型安全与资源管理效... 目录C++11新特性1. 自动类型推导auto1.1 基本语法2. decltype3. 列表初始化3

C++11右值引用与Lambda表达式的使用

《C++11右值引用与Lambda表达式的使用》C++11引入右值引用,实现移动语义提升性能,支持资源转移与完美转发;同时引入Lambda表达式,简化匿名函数定义,通过捕获列表和参数列表灵活处理变量... 目录C++11新特性右值引用和移动语义左值 / 右值常见的左值和右值移动语义移动构造函数移动复制运算符

C++中detach的作用、使用场景及注意事项

《C++中detach的作用、使用场景及注意事项》关于C++中的detach,它主要涉及多线程编程中的线程管理,理解detach的作用、使用场景以及注意事项,对于写出高效、安全的多线程程序至关重要,下... 目录一、什么是join()?它的作用是什么?类比一下:二、join()的作用总结三、join()怎么

C++中全局变量和局部变量的区别

《C++中全局变量和局部变量的区别》本文主要介绍了C++中全局变量和局部变量的区别,全局变量和局部变量在作用域和生命周期上有显著的区别,下面就来介绍一下,感兴趣的可以了解一下... 目录一、全局变量定义生命周期存储位置代码示例输出二、局部变量定义生命周期存储位置代码示例输出三、全局变量和局部变量的区别作用域