C++11续——智能指针(出现原因至源码模拟)

前言:在C++11里面提出了一个新的语法 try catch用来捕捉异常,这样子能不使用return和exit的前提下退出程序就得到错误信息,但是随之而来的就是一个新的问题,try  catch退出程序之后可能带来了无法释放的内存泄露问题,原因是try catch是跳跃式捕捉的。

目录

一,try  catch带来的内存泄漏问题

二,智能指针

1,auto_ptr

1)简单使用

2)源码模拟

3)auto_ptr的缺陷

2,unique_ptr

3,shared_ptr

1)引言

2)源码模拟

 3)shared_ptr的缺陷

4,weak_ptr


一,try  catch带来的内存泄漏问题

大家先看一段代码

class A {
public:A() {d = new int(666);}~A() {cout << "delete d" << endl;delete d;}
private:int* d;
};
void test02() {int* a = new int(10);int* b = new int(0);A d;if (*b == 0)throw "除零错误";int c = *a / *b;cout << "delete a"<<endl;delete a;cout << "delete b" << endl;delete b;
}
int main() {try {test02();}catch(const char* s){cout << s << endl;}return 0;
}

大家有没有想到这段简单的代码有致命的错误,没错就是内存泄漏,但是里面有类开辟的靠近,也有函数自己手动开辟的空间,到底哪些空间没有被释放呢?

 答案是类会被调用析构函数释放空间,而函数自己开辟的空间无法被释放,因为代码全部被跳过不执行了,这样子在我们编写大型程序时,如果不加以约束和处理,内存泄漏将会是巨大的问题,那么我们有没有解决办法呢?

二,智能指针

答案是有解决办法,我们发现虽然函数的代码段被跳过了,但是创建的类的析构函数还是会被调用,那么我们如果利用一个类来帮助我们自动管理这些开辟的空间不就可以避免内存泄漏了吗?有了思路现在我们开始本文正片——智能指针。

1,auto_ptr
1)简单使用

这个auto_ptr是C++98提出来的,但是它有一个比较致命缺陷,C++11官方也提出了解决办法,我们这里先不讲他的缺陷,大家先看它的用法及原理,后面我会引导大家理解它的缺陷。

	//简单使用auto_ptrauto_ptr<int> a ( new int(10));cout << *a;

 除此之外auto_ptr还支持,一些运算符重载

大家想要仔细研究可以打开

auto_ptr - C++ Referenceicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/memory/auto_ptr/?kw=auto_ptr

2)源码模拟

如果让我们写一个auto_ptr,我们该如何下手呢?首先auto_ptr本质就是一个类容器,我们利用模板,就能实现识别指针应该是什么类型,然后我们在里面重新定义一个指针,指向传来开辟的空间不就行了吗?

从这个思路出发,我们先写出类的基本框架

namespace bit {template<class P>class auto_ptr {public:auto_ptr(P* p) {this->p = p;}~auto_ptr() {delete p;p = nullptr;}private:P* p;};
};

上面的代码不就实现了一个类自动管理指针开辟的空间了吗?

至于里面的一些函数功能,相信学到智能指针这块的我们早已经轻车熟路了,如果实在不懂可以参考我往期博客STL源码刨析。

namespace bit {//auto_ptr没有解决复制拷贝的问题//当拷贝的时候,被拷贝的auto_ptr的管理权就丧失了,其管理的资源置为了空,//并且auto_ptr无法管理数组template<class P>class auto_ptr {public:auto_ptr(P* p) {this->p = p;}auto_ptr(bit::auto_ptr<P>& a) {p = a.p;a.get() = nullptr;}P* get() {return p;}P* operator->() {return p;}P& operator*() {return *p;}auto_ptr& operator=(auto_ptr a) {p = a.get();a.get() = nullptr;return this;}~auto_ptr() {delete p;p = nullptr;}private:P* p;};
};
3)auto_ptr的缺陷

 auto_ptr有什么缺陷呢?答案是=重载和拷贝构造,大家先看我运行一段代码及运行结果

这是为什么呢?因为auto_ptr不支持多个智能指针指向同一块空间,因此当我们访问被赋值或者被拷贝构造的原指针就会出现报错,因为赋值或者拷贝构造完成后原指针指向空间会被变成nullptr,这就带来了一个问题,如果我们在接下来的代码里面一旦不小心访问到了原指针就会导致程序报错崩溃。

很多人就想说了,我直接多个指针指向同一块空间不就行了?但是又因此衍生出来了一个问题,那就是析构函数的时候,空间只能释放一次,但是这么多auto_ptr指向这块空间该由谁来析构呢?大家不用担心,我会在shared_ptr讲解决方案的。

2,unique_ptr

unique_ptr作为auto_ptr的一个优化版本,它的解决办法堪称简单粗暴,既然你的拷贝构造和赋值有问题,那我直接把它们定义成私有(相当于delete,外部无法调用,自然相当于被禁止了)不准你们使用不就行了吗?使用方法和auto_ptr差不多,我们就直接看源码模拟吧

namespace bite
{template<class T>class unique_ptr{// RAIIpublic:unique_ptr(T* ptr = nullptr): _ptr(ptr){}~unique_ptr(){if (_ptr)delete _ptr;}// 具有指针类似行为T& operator*(){return *_ptr;}T* operator->(){return _ptr;}// 防止被拷贝--禁止调用拷贝构造&赋值运算符重载
#if 0// C++98:只声明不定义 & privateprivate:unique_ptr(const unique_ptr<T>& up);unique_ptr<T>& operator=(const unique_ptr<T>& up);
#endifunique_ptr(const unique_ptr<T>& up) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& up)=delete;protected:T* _ptr;};
}
3,shared_ptr
1)引言

unique_ptr终归有点简单粗暴了,我们还是有一些多个指针指向同一块空间的使用场景,那我们该如何处理这一块空间呢?

这就不得不涉及到一个巧妙的解决方案了,所有指向同一块空间的智能指针里面都存储一个指针,这个指针里面存储指向这块空间的人数,当人数减为0的时候析构函数才真正的释放资源,否则就减减,这样子就能保证空间一定是最后一个使用的人释放,不会出现同一块空间被多次释放的问题了。

那么话不多说,我们直接开始源码模拟吧。

2)源码模拟

首先我们讲解赋值拷贝

我们只需要将count++即可

	template<class T>class shared_ptr {public:	shared_ptr(shared_ptr<T>& s) {data = s.data;count = s.count;(*count)++;}private:int* count;T* data;};

=号重载有一个小坑,那就是原本的shared_ptr已经指向一块空间了,我们不能想赋值构造那样子无脑的赋值,我们需要先把原本指向的空间进行count--,如果count--之后等于0,那么我们就必须将空间释放再赋值。

shared_ptr& operator=(const shared_ptr<T>& s) {if (data != s.data) {if (-- * count == 0) {delete data;}(*s.count)++;data = s.get();count = s.count;}return *this;}

 其他的也就一个析构函数再谈一下吧,析构函数需要检查count,判断释放需要释放资源。

~shared_ptr() {if (--*count == 0) {delete data;delete count;cout << "delete" << endl;}}

完整源码

	template<class T>class shared_ptr {public:shared_ptr(shared_ptr<T>& s) {data = s.data;count = s.count;(*count)++;}shared_ptr(T* s=nullptr) {data = s;count = new int{ 1 };}T& operator*() {return *data;}T* operator->() {return data;}shared_ptr& operator=(const shared_ptr<T>& s) {if (data != s.data) {if (-- * count == 0) {delete data;cout << "delete" << endl;}(*s.count)++;data = s.get();count = s.count;}return *this;}T* get() {return data;}~shared_ptr() {if (--*count == 0) {delete data;delete count;cout << "delete" << endl;}}private:int* count;T* data;};
 3)shared_ptr的缺陷

大家看上面的shared_ptr是不是很好用,代码应该也没有错误,答案是否,大家先看一段shared_ptr经典内存泄漏代码。

	struct list {shared_ptr<list> prv;shared_ptr<list> next;};shared_ptr<list> head(new list), l1(new list);head->next = l1;l1->prv = head;

为什么说这段代码会导致内存泄漏呢? 

首先我们知道类的析构函数调用顺序,首先是调用本身的析构函数,然后调用类里面的成员类的析构函数,这样子导致了一个问题,当head调用析构函数的时候,它的count为2,减减之后为1,无法正常析构,为什么无法正常析构呢?因为head和l1d空间都是new出来的,是无法主动调用类里面的子类的析构函数将count变为0释放空间,导致最后两个count都是1,空间任然没有被正常释放,那有什么解决办法吗?且看下文讲解

4,weak_ptr

上面说了shared_ptr由于循坏引用导致死循坏,这个时候weak_ptr就应运而生了,weak_ptr也是一种智能指针,而且和shared_ptr能够相互配合使用(限用于循坏引用等情况),它作为shared_ptr的附庸存在,不能单独使用,可能会导致空间资源被多次释放。

这是怎么实现解决循坏引用的问题呢?答案是很简单,虽然shared_ptr能和weak_ptr配合使用,但是weak_ptr和shared_ptr指向同一块空间,weak_ptr并不会引起count的大小,这样子就完美解决了循坏引用的问题。

	struct list {weak_ptr<list> prv;weak_ptr<list> next;};shared_ptr<list> head(new list), l1(new list);head->next = l1;l1->prv = head;

源码模拟并不困难,在shared_pt的构造函数添加一个weak_ptr的构造函数,在weak_ptr的构造函数里面加上一个shared_ptr的构造函数就行了。

shared_ptr::shared_ptr - C++ Referenceicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/memory/shared_ptr/shared_ptr/ 

weak_ptr::weak_ptr - C++ Referenceicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/memory/weak_ptr/weak_ptr/

这里便不再进行源码模拟,留给大家练手吧。如果大家有所收获希望点赞加收藏。

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

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

相关文章

华为涅槃,余承东重生

最近一段时间&#xff0c;余承东甚为低调。最为明显的是&#xff0c;“遥遥领先”已经听不到了&#xff0c;“余大嘴”口中的措辞越来越克制。 今后手机相关的发布会&#xff0c;或许不再看到余承东的身影。 5月10日&#xff0c;余承东的职位正式更新&#xff0c;从终端BG CE…

Mysql数据存储格式分析

一、整体存储逻辑 1.1 Mysql数据存放位置 不同的存储引擎&#xff0c;对Mysql数据的存储是不同的。新建一个test数据库&#xff0c;里面有t1,t2和test5三张表&#xff0c;以Innodb和Myisam存储引擎为例&#xff1a; Innodb存储引擎&#xff1a; .frm文件&#xff1a;与表相…

【HR】阿里三板斧--20240514

参考https://blog.csdn.net/haydenwang8287/article/details/113541512 头部三板斧 战略能不能落地、文化能不能得到传承、人才能不能得到保障。 头部三板斧适用的核心场景有三个&#xff1a;一是战略不靠谱&#xff1b;二是组织效率低、不聚心&#xff1b;三是人才跟不上。对…

每日一学—K邻算法:在风险传导中的创新应用与实践价值

文章目录 &#x1f4cb; 前言&#x1f3af; K邻算法的实践意义&#x1f3af; 创新应用与案例分析&#x1f525; 参与方式 &#x1f4cb; 前言 在当今工业领域&#xff0c;图思维方式与图数据技术的应用日益广泛&#xff0c;成为图数据探索、挖掘与应用的坚实基础。本文旨在分享…

思科模拟器学习1--Vlan Trunk

实验说明&#xff1a;将三台电脑的vlan 加到一台交换机里面&#xff0c;为了验证什么是虚拟局域网&#xff0c;把一个设备隔成三个空间&#xff0c;三个电脑互相不能通讯&#xff1b;目的是&#xff1a;vlan 1的通讯不可以向vlan 2传送&#xff0c;就是消息传送互不干扰的&…

网络完全精通版

一、目录结构 1.1目的的特点 windows和linux windows中C、D、E盘&#xff0c;每个都是一个根系统【多跟系统】 linux中只有一个根【单根系统】 1.2各个目录存储的内容 /root&#xff1a;linux中挂管理员用户的家目录 /home&#xff1a;linux中挂存储普通用户的家目录的目…

linux系统修改网卡名称

说明&#xff1a; 因操作过程需要停用网卡&#xff0c;导致ssh远程连接不上&#xff0c;需要控制台登录操作。 测试环境&#xff1a; CentOS7.9、8.2虚拟机 Suse15 SP4虚拟机 操作步骤&#xff1a; 方法一&#xff1a; 1、 查看网卡当前名称及状态 ip a2、 将网卡状态从启用…

刷题之最长连续序列

哈希表 class Solution { public:int longestConsecutive(vector<int>& nums) {//set记录并且去重nums中的数unordered_set<int>set;for(int i0;i<nums.size();i){set.insert(nums[i]);}int result0;//遍历所有数for(auto iset.begin();i!set.end();i){//如…

怎样计算Excel一列数值中十位数为5的个数?

有一列数字&#xff0c;可能正数也可能是负数&#xff0c;有可能有小数&#xff0c;要怎么计算这列数字中十位数为5的数量有多少个&#xff1f; 一、按示例情况&#xff0c;数字均为整数 公式如下&#xff1a; SUM(--(MID(A1:A6,LEN(A1:A6)-1,1)"5")) 数组公式&a…

多臂老虎机

多臂老虎机 有n根拉杆的的老虎机&#xff0c;每根拉杆获得奖励(值为1)的概率各不相同。 期望奖励更新 Q k 1 k ∑ i 1 k r i 1 k ( r k ∑ i 1 k − 1 r i ) 1 k ( r k k Q k − 1 − Q k − 1 ) Q k − 1 1 k [ r k − Q k − 1 ] Q_k\frac 1k \sum^{k}_{i1}r_i\\…

机器学习笔记 PostgresML教程:使用SQL进行机器学习

机器学习的基本做法是将数据转移到模型的环境中进行训练。由于今天的数据库比机器学习模型大好多个数量级,所以PostgresML的思路是,如果我们将模型引入数据集不是会容易得多吗? PostgresML 是一个建立在流行的 PostgreSQL 数据库之上的综合机器学习平台。它引入了一种称为“…

嵌入式学习-通用定时器

简介 框图介绍 时钟选择 计数器部分 输入捕获和输出比较框图 嵌入式学习全文参考&#xff08;小向是个der&#xff09;做笔记&#xff1a;https://blog.csdn.net/qq_41954556/article/details/129735708