【1++的C++进阶】之智能指针

👍作者主页:进击的1++
🤩 专栏链接:【1++的C++进阶】

文章目录

  • 一,什么是智能指针
  • 二,为什么需要智能指针
  • 三,智能指针的发展

一,什么是智能指针

要了解智能指针,我们先要了解RAII.
RAII是一种利用对象生命周期来控制资源的技术。
在对象初始化时,其接管资源,在对象的生命周期内其管理的资源始终保持有效,最后当对象析构时,释放资源。

那么什么是智能指针呢?
智能指针就是利用了RALL的原理,并且通过封装,使得它的对象能够向指针一样使用。因此其重载了*,->。

下面是一个简单的智能指针代码:

template<class T>class smart_ptr{public:smart_ptr(T* ptr):_ptr(ptr){}~smart_ptr(){if (_ptr){delete _ptr;}}T& operator*(){return *_ptr;}T* operator ->(){return &_ptr;}private:T* _ptr;};

二,为什么需要智能指针

我们来看一段代码:

double divide(int a, int b)
{if (b == 0)throw "除0错误";elsereturn a / b;
}
void func()
{int* arr = new int[10];//申请空间try{int a, b;cin >> a >> b;divide(a, b);}catch (...){//在这里进行校对后,再抛出delete[]arr;cout << "delete[]arr" << endl;throw;}delete []arr;//若出现异常,则不会执行到这里,会造成内存泄漏。cout << "delete[]arr" << endl;}
int main()
{try{func();}catch (const char* errmsg){cout << errmsg << endl;}catch (...){cout << "错误" << endl;}return 0;
}

上述代码中,我们为了防止内存泄漏,因此进行了校正,再抛出的操作,这样做相对来说比较麻烦,而且我们在写代码时也是容易忘记校正的操作,这时就需要我们的智能指针了。我们在申请资源的函数内部实例化除一个智能指针的对象,将资源交给它管理,当这个函数调用完成退出后,这个对象也会进行析构,其管理的资源也就释放了。是不是相当方便。

上述提到了内存泄漏,接下来我们就专门来谈一谈内存泄漏。
什么是内存泄漏呢?
内存泄漏是指由于我们的失误,未能释放我们已经不适用的内存,而造成资源的浪费。也可以说是,我们由于设计错误,而对该段内存失去了控制权,进而造成了空间的浪费。

内存泄漏分类:

  1. 堆内存泄漏(Heap leak)
    堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak
  2. 系统资源泄漏
    指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

三,智能指针的发展

在我们前面写的智能指针中我们会发现其不能够拷贝构造和赋值。因为若我们用默认生成的拷贝构造或赋值的话,由于两个指针指向同一块空间,那么这块空间在释放时就会被释放两次而导致错误。

因此在C++98中就有了auto_ptr的出现,它在面对拷贝构造和赋值问题的解决办法是:管理权的转移-----也就是:当通过A构造出或将A赋值给B时,A的管理权转让给B,A不再进行管理。
这样的设计在后来经常为人所诟病。因为其在拷贝或赋值后,原来的对象指向的空间会被置空,也就是失去了管理权。
下面是其实现代码:

template<class T>class auto_ptr{public:auto_ptr(T* ptr):_ptr(ptr){}auto_ptr(auto_ptr& s){_ptr = s._ptr;s._ptr = nullptr;}auto_ptr& operator=(auto_ptr s){if (s._ptr != this->_ptr){delete _ptr;_ptr = s._ptr;s._ptr = nullptr;}return *this;}~auto_ptr(){if (_ptr){delete _ptr;}}T& operator*(){return *_ptr;}T* operator ->(){return &_ptr;}T* get(){return _ptr;}private:T* _ptr;};

由于auto_ptrd的缺陷,在C++11中出现了unique_ptr。
unique_ptr的的原理非常粗暴----既然拷贝和赋值是个坑,那我直接禁用你。。。
因此在unique_ptr中,其主要改变就是禁用了拷贝和赋值。
禁用拷贝和赋值有下面几种方法:

  1. 成员函数私有化。
  2. 只声明不定义
  3. 用delete关键字修饰。

下面是unique_ptr的实现:

template<class T>class unique_ptr{public:unique_ptr(T* ptr):_ptr(ptr){}unique_ptr(unique_ptr& s) = delete;unique_ptr<T>& operator=(unique_ptr s) = delete;~unique_ptr(){if (_ptr){delete _ptr;}}T& operator*(){return *_ptr;}T* operator ->(){return &_ptr;}T* get(){return _ptr;}private:T* _ptr;};

但是,在有些场景下两个对象管理同一块空间的这种需求还是有的,因此C++11中还出现了更加靠谱的shared_ptr。
其原理就是增加了引用计数。对于同一块资源,每赋值或拷贝一次,计数就加1 。每析构一个对象就减一。直到为0也就是只有一个对象在维护着这块空间时,其就释放这块资源。

下面是shared_ptr的实现:


template<class T>class shared_ptr{public:shared_ptr(T* ptr):_ptr(ptr), count_ptr(new int(1)){}shared_ptr(const shared_ptr& s):_ptr(s._ptr){(*s.count_ptr)++;count_ptr = s.count_ptr;}shared_ptr<T>& operator=(const shared_ptr& s){if (_ptr != s._ptr){_ptr = s._ptr;(*s.count_ptr)++;count_ptr = s.count_ptr;}return *this;}~shared_ptr(){if (*count_ptr > 1){(*count_ptr)--;}else{delete _ptr;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get()const{return _ptr;}private:T* _ptr;int* count_ptr;};

但是,shared_ptr也是有一些问题的。

  1. 线程安全问题----我们在后面的文章会进行讲解。
  2. 循环引用问题和对象的删除问题

接下来我们讲 2 中的两个问题。
循环引用问题:
我们先来看看什么是循环引用问题:
在这里插入图片描述
如图,就是我们的循环引用,其到底会产生什么问题呢?
我们慢慢往下看。
当我们node1,node2析构后,其计数都为1,还并没有释放掉。只有_next释放掉,node2才能释放,_prev释放掉,node1才能释放。而只有node1释放掉_next才能释放,node2释放,_prev才能释放。
这就形成了一个死循环。谁的释放不了。
因此引入了weak_ptr。
其就是用来解决循环引用问题的。
其解决循环引用的原理就是:在引用计数的场景下,把节点中的_prev和_next改成weak_ptr类型。node1->_next = node2;和node2->_prev = node1;时weak_ptr的_next和_prev不会增加node1和node2的引用计数。
这样在析构node1和node2时,其管理的资源也都进行了释放。

下面是weak_ptr的实现代码:

template<class T>class weak_ptr{public:weak_ptr():_ptr(nullptr){}weak_ptr(const shared_ptr<T>& s):_ptr(s.get()){}weak_ptr<T>& operator=(const shared_ptr<T>& s){_ptr = s.get();return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};

我们再来解决删除的问题:
我们在《内存管理》这篇文章中讲过new 和delete要搭配使用,new …[n]要与delete [] …,malloc与free搭配…
并且我们讲了new和malloc的区别,delete与free的区别。
那么new 与new[] ,delete与delete[]有什么区别呢?
下面我们来用一张图讲解清楚:

在这里插入图片描述
可见delete与delete[]区别在于调用析构函数的次数和释放空间的指针的位置。因此,当我们用delete去释放一个本该用delete[]释放的对象时,便会发生错误。

我们也不能去修改库里的析构函数。那我们该怎么解决呢?
在这里插入图片描述

C++11中给我们提供了在构造智能指针对象时可以传一个删除器的对象来应对不同方式申请的对象。
下面我们用仿函数的形式进行模拟:
代买实现如下:

template<class T,class D>class shared_ptr{public:shared_ptr(T* ptr):_ptr(ptr), count_ptr(new int(1)){cout << "构造" << endl;}shared_ptr(const shared_ptr& s):_ptr(s._ptr){(*s.count_ptr)++;count_ptr = s.count_ptr;cout << "拷贝构造" << endl;}shared_ptr<T,D>& operator=(const shared_ptr& s){if (_ptr != s._ptr){_ptr = s._ptr;(*s.count_ptr)++;count_ptr = s.count_ptr;cout << "赋值" << endl;}return *this;}~shared_ptr(){if (*count_ptr > 1){(*count_ptr)--;cout << "count--" << endl;}else{D()(_ptr);//cout << "delete" << endl;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get()const{return _ptr;}private:T* _ptr;int* count_ptr;};删除器template<class T>class Free{public:void operator()(T* ptr){cout << "Free" << endl;free(ptr);}};template<class T>class Delete{public:void operator()(T* ptr){cout << "Delete" << endl;delete ptr;}};template<class T>class Delete_{public:void operator()(T* ptr){cout << "Delete_" << endl;delete [] ptr;}};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/112152.html

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

相关文章

K8S pod资源、探针

目录 一.pod资源限制 1.pod资源限制方式 2.pod资源限制指定时指定的参数 &#xff08;1&#xff09;request 资源 &#xff08;2&#xff09; limit 资源 &#xff08;3&#xff09;两种资源匹配方式 3.资源限制的示例 &#xff08;1&#xff09;官网示例 2&#xff0…

C#,《小白学程序》第二十六课:大数乘法(BigInteger Multiply)的Toom-Cook 3算法及源程序

凑数的&#xff0c;仅供参考。 1 文本格式 /// <summary> /// 《小白学程序》第二十六课&#xff1a;大数&#xff08;BigInteger&#xff09;的Toom-Cook 3乘法 /// Toom-Cook 3-Way Multiplication /// </summary> /// <param name"a"></par…

WebGL 视图矩阵、模型视图矩阵

目录 立方体由三角形构成 视点和视线 视点、观察目标点和上方向 视点&#xff1a; 观察目标点&#xff1a; 上方向&#xff1a; 在WebGL中&#xff0c;观察者的默认状态应该是这样的&#xff1a; 视图矩阵程序&#xff08;LookAtTriangles.js&#xff09; 实际上&…

【资源监视器】设备占用,强制弹出移动硬盘

设备占用&#xff0c;强制弹出移动硬盘 任务管理器中找到资源监视器 资源监视器&#xff1a;找到CPU 输入磁盘&#xff1a;如H: , 点击旁边的刷新 结束句柄 右键

python-字符串去掉空格的常见方法

python提供了去掉字符串空格的方法&#xff0c;可以满足大部分需求。 但在实际应用中&#xff0c;还需要灵活借助python其他方法&#xff0c;来实现字符串空格的删除。 比如&#xff0c;去掉字符串的全部空格、字符串连续空格保留一个等&#xff0c;都需要结合其他的方法来实现…

Postman接口压力测试 ---- Tests使用(断言)

所谓断言&#xff0c;主要用于测试返回的数据结果进行匹配判断&#xff0c;匹配成功返回PASS&#xff0c;失败返回FAIL。 下图方法一&#xff0c;直接点击右侧例子函数&#xff0c;会自动生成出现在左侧窗口脚本&#xff0c;只需修改数据即可。 方法二&#xff1a;直接自己写脚…

vue中slot,slot-scope,v-slot的用法和区别

slot用于设置标签的属性值(slot“title”)slot-scopev-slot slot <el-menu-item v-if"!navMenu.children" :key"navMenu.id" :index"navMenu.id " click"itemClick(navMenu)" ><span slot"title">{{ navMenu.…

UMA 2 - Unity Multipurpose Avatar☀️九.Expressions表情管理与表情插件推荐 (口型同步 / 表情管理)

文章目录 🟥 Expressions文件位置🟧 功能 : 解决嘴巴张开问题🟨 Expressions : 表情面板API讲解🟩 表情插件推荐 : 口型同步 / 表情管理🟥 Expressions文件位置 Expressions也是UMA内置5种实用Recipes之一,位置如下. 使用方法: 如下图所示,将Recipes拖到Additional…

数据结构-----队列

目录 前言 队列 定义 队列的定义和操作方法 队列节点的定义 操作方式 顺序表实现队列&#xff08;C/C代码&#xff09; 链表实现队列&#xff08;C/C代码&#xff09; Python语言实现队列 前言 排队是我们日常生活中必不可少的一件事&#xff0c;去饭堂打饭的时候排队&a…

实现数组去重的七种方法

实现数组去重的 7 种方式 1. 方法一&#xff1a;利用两层循环数组的splice方法 通过两层循环对数组元素进行逐一比较&#xff0c;然后通过splice方法来删除重复的元素。此方法对NaN是无法进行去重的&#xff0c;因为进行比较时NaN ! NaN。 let arr [1, 2, 2, abc, abc, true,…

LeetCode: 4. Median of Two Sorted Arrays

LeetCode - The Worlds Leading Online Programming Learning Platform 题目大意 给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。 请你找出这两个有序数组的中位数&#xff0c;并且要求算法的时间复杂度为 O(log(m n))。 你可以假设 nums1 和 nums2 不会同时为空。 …

【笔试强训选择题】Day43.习题(错题)解析

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;笔试强训选择题 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01;&#xff…