手机网站的特点,吉林省建设厅网站杨学武,上海市中小企业服务中心,夫唯seo#x1f493;博主CSDN主页:杭电码农-NEO#x1f493; ⏩专栏分类:C从入门到精通⏪ #x1f69a;代码仓库:NEO的学习日记#x1f69a; #x1f339;关注我#x1faf5;带你学习C #x1f51d;#x1f51d; C11 1. 前言2. 为什么要有智能指针?3. RAII思想… 博主CSDN主页:杭电码农-NEO ⏩专栏分类:C从入门到精通⏪ 代码仓库:NEO的学习日记 关注我带你学习C C11 1. 前言2. 为什么要有智能指针?3. RAII思想以及智能指针的设计4. C智能指针的发展历史5. shared_ptr模拟实现6. shared_ptr的循环引用问题7. 定制删除器8. 总结以及拓展 1. 前言
相信学C的同学或多或少的听说过 智能指针这个词,博主刚听见这个词时 ,觉得它应该很复杂,并且很高大上,但不 管是多牛的东西,都是人写出来的,是可 学习的!不要怀着害怕的心理来学习它
本章重点: 本篇文章着重讲解智能指针的发展历史 中出现过的auto_ptr,unique_ptr以及主 角shared_ptr.并且会介绍什么是RAII思想 以及为什么要有智能指针这一话题,最后 会给大家分析shared_ptr的循环引用问题 以及定制删除器的基本概念 2. 为什么要有智能指针?
在写代码时,我们经常在堆上申请空间 但是偶尔会忘记释放空间,会造成内存 泄漏问题,当然,这不是最重要的,在某些 场景下即使你释放了也会有问题:
int div()
{int a, b;cin a b;if (b 0)throw invalid_argument(除0错误);return a / b;
}
void Func()
{// 1、如果p1这里new 抛异常会如何// 2、如果p2这里new 抛异常会如何// 3、如果div调用这里又会抛异常会如何int* p1 new int;int* p2 new int;cout div() endl;delete p1;delete p2;
}
int main()
{try{Func();}catch (exception e){cout e.what() endl;}return 0;
}在上面代码的这种场景中,不管是使用 new还是调用div函数都有抛异常的风险 并且程序一旦抛异常就会直接跳到catch 处,所以上面的代码一旦抛异常就代表着 delete p1和p2并不会执行,也就会出现 内存泄漏的问题!这个问题不使用智能 指针是很难解决的!!! 3. RAII思想以及智能指针的设计
RAII思想
RAII思想是一种 利用对象生命周期来控制程序资源 如内存、文件句柄、网络连接、互斥量等等的简单技术。在对象构造时获取资源接着控制对资源的访问使之在对象的生命周期内始终保持有效最后在对象析构的时候释放资源。借此我们实际上把管理一份资源的责任托管给了一个对象
这种做法有两种好处:
不需要显式地释放资源对象所需的资源在其生命期内始终有效
智能指针的基本设计
现在我们来写一个类,构造函数的 时候创造资源,析构函数的时候释放 资源,当对象出了作用域会自动调用析构!
// 使用RAII思想设计的SmartPtr类
templateclass T
class SmartPtr {
public:
SmartPtr(T* ptr nullptr): _ptr(ptr)
{}
~SmartPtr()
{if(_ptr!nullptr)delete _ptr;
}
T operator*() {return *_ptr;}
T* operator-() {return _ptr;}
private:T* _ptr;
};现在我们来使用一下它:
SmartPtrint sp1(new int(10));
*sp 20;当然,重载了-是给自定义类型用的 4. C智能指针的发展历史 首先,我们要清楚智能指针的一个大坑 那就是当一个指针赋值给另外一个指针 时,我们需要的是浅拷贝,因为我们就是想 让两个指针指向同一块空间,但是指向了 同一块空间就会有析构函数调用两次的风险 由于这一个大坑,智能指针进行了很多次迭代 在C98的时候就已经在库中实现 了智能指针了,它就是 auto_ptr 既然智能指针是随着历史不断发展的 就证明它前面的版本写的不咋滴[doge] 事实也是如此,auto_ptr是这样实现的, 既然有析构两次的风险,那么当我把A 指针赋值给B指针后,A指针就销毁不能用 了,对于不了解auto_ptr的人来说这无疑是 一个巨大的风险! auto_ptrint ap1(new int(10));
auto_ptrint ap2(ap1);
//此时ap1已经失效了!有了这一大坑后,C11推出了全新 的智能指针: unique_ptr unique_ptr的做法比auto_ptr还绝 智能指针不是拷贝有问题吗?那么 unique_ptr就禁用了拷贝和赋值, 很显然这也是一个坑,但是在实际 场景下,unique_ptr至少还能被用到 但auto_ptr是很多公司明令禁止使用的! unique_ptrint up1(new int(10));
unique_ptrint up2(up1);//这里会直接报错经过两次失败的智能指针后,C11 还推出了今天的主角: shared_ptr shared_ptr可堪称完美的智能指针 也是实际中使用的最多的智能指针 它采用的是引用计数的思想,当指向 这份空间的计数是1时才析构,大于1 时就将计数减一,非常的优雅! 由于智能指针在面试时让手撕的概率很大 所以我们会模拟实现它 5. shared_ptr模拟实现
我们使用引用计数的方式来实现 shared_ptr,也就是在原先代码的 基础上增加一个int*成员变量来保存 还有几个指针指向当前空间!
templateclass T
class Smart_Ptr //实现的C11的shared_ptr版本
{
public:Smart_Ptr(T* ptr nullptr):_ptr(ptr),_pcount(new int(1)){}~Smart_Ptr(){Release();}Smart_Ptr(const Smart_PtrT sp):_ptr(sp._ptr),_pcount(sp._pcount){Addcount();}Smart_PtrT operator(const Smart_PtrT sp){if (_ptr ! sp._ptr){Release();_ptr sp._ptr;_pcount sp._pcount;Addcount();}return *this;}void Release(){if (--(*_pcount) 0)//销毁最后一个变量时才释放资源{delete _ptr;delete _pcount;delete _pmtx;}}void Addcount(){(*_pcount);}void Subcount(){Release();private:T* _ptr;int* _pcount;
};我们将计数贺计数- -特意的提出来 这是因为很多场景下都需要这两个函数. 当计数不为1时就- -计数,当计数为一才 释放资源,并且这样写的好处是相同类型 的指针对象即使指向不同的空间也不会 出错,相反,使用static定义成员指针变量 就会出现上面的这种问题! 6. shared_ptr的循环引用问题
请看下面的代码运行会崩溃:
struct ListNode
{int _data;shared_ptrListNode prev;shared_ptrListNode next;~ListNode(){ cout ~ListNode() endl; }
};
int main()
{shared_ptrListNode node1(new ListNode);shared_ptrListNode node2(new ListNode);node1-next node2;node2-prev node1;return 0;
}为啥会崩溃?下面我用画图加文字 的方式帮大家分析一下此问题: 现在来进一步分析:当main函数调用完, node2会先析构,但是此时引用计数是2 所以不会释放空间而是将计数变为1. 然后node1再析构,同上,它的引用计数 也减为一,但是这两份空间并不会释放, 因为要node2的prev释放后,node1的空间 才会释放,那node2的prev什么时候释放? 答案是node2这份空间释放了才会释放 prev,那么node2这份空间什么时候释放? 答案是node1的next释放了它才释放,这 就形成了一个死循环,我等你释放了我才 能释放,对方也在等我释放了对方才能 释放,这就是循环引用问题 最好的解决方案就是在使用智能指针 的时候跳过这个坑,不用将智能指针和 这种场景一起使用!!! 7. 定制删除器
使用智能指针时可能会遇见下面的问题:
shared_ptrint sp1(new int[10]);当变量出作用域销毁时即报错 因为new []对应的是delete []. 然而库中写法并不能识别有没有[]
还有一些问题:
shared_ptrFILE sp3(fopen(Test.cpp, r));此时智能指针管理的对象并不是堆上 开辟的空间,delete完全没法用,此时需 要使用fclose,所以定制删除器非常重要 在构造函数的地方可以传入一个定制 删除器,也就是一个函数对象,此函数 中有对应的删除方法,请看下面的代码: shared_ptrint sp2(new int[10], [](int* ptr) {delete[] ptr; });
shared_ptrFILE sp3(fopen(Test.cpp, r), [](FILE* ptr) {fclose(ptr); });注:定制删除器属于了解的部分 8. 总结以及拓展
智能指针在面试中是常客,经常会被 问到发展历史和shared_ptr的手撕, 学到这里后,C的所有重要的知识 差不多已经完结了,后面文章更新会慢一点
拓展:weak_ptr的拓展阅读
既然weak_ptr可以解决shared_ptr的 循环引用问题,那么什么是weak_ptr? 有兴趣的同学可以阅读下面这篇文章:
weak_ptr详解 下期预告:C异常的处理方式