金泉网 网站建设,站群软件,营口市住房建设保障办官方网站,套模板做网站教程#x1f44d;作者主页#xff1a;进击的1 #x1f929; 专栏链接#xff1a;【1的C进阶】 文章目录 一#xff0c;什么是智能指针二#xff0c;为什么需要智能指针三#xff0c;智能指针的发展 一#xff0c;什么是智能指针
要了解智能指针#xff0c;我们先要了解RA… 作者主页进击的1 专栏链接【1的C进阶】 文章目录 一什么是智能指针二为什么需要智能指针三智能指针的发展 一什么是智能指针
要了解智能指针我们先要了解RAII. RAII是一种利用对象生命周期来控制资源的技术。 在对象初始化时其接管资源在对象的生命周期内其管理的资源始终保持有效最后当对象析构时释放资源。
那么什么是智能指针呢 智能指针就是利用了RALL的原理并且通过封装使得它的对象能够向指针一样使用。因此其重载了*-。
下面是一个简单的智能指针代码
templateclass Tclass 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;
}
上述代码中我们为了防止内存泄漏因此进行了校正再抛出的操作这样做相对来说比较麻烦而且我们在写代码时也是容易忘记校正的操作这时就需要我们的智能指针了。我们在申请资源的函数内部实例化除一个智能指针的对象将资源交给它管理当这个函数调用完成退出后这个对象也会进行析构其管理的资源也就释放了。是不是相当方便。
上述提到了内存泄漏接下来我们就专门来谈一谈内存泄漏。 什么是内存泄漏呢 内存泄漏是指由于我们的失误未能释放我们已经不适用的内存而造成资源的浪费。也可以说是我们由于设计错误而对该段内存失去了控制权进而造成了空间的浪费。
内存泄漏分类
堆内存泄漏(Heap leak) 堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放那么以后这部分空间将无法再被使用就会产生Heap Leak系统资源泄漏 指程序使用系统分配的资源比方套接字、文件描述符、管道等没有使用对应的函数释放掉导致系统资源的浪费严重可导致系统效能减少系统执行不稳定。
三智能指针的发展
在我们前面写的智能指针中我们会发现其不能够拷贝构造和赋值。因为若我们用默认生成的拷贝构造或赋值的话由于两个指针指向同一块空间那么这块空间在释放时就会被释放两次而导致错误。
因此在C98中就有了auto_ptr的出现它在面对拷贝构造和赋值问题的解决办法是管理权的转移-----也就是当通过A构造出或将A赋值给B时A的管理权转让给BA不再进行管理。 这样的设计在后来经常为人所诟病。因为其在拷贝或赋值后原来的对象指向的空间会被置空也就是失去了管理权。 下面是其实现代码
templateclass Tclass 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的缺陷在C11中出现了unique_ptr。 unique_ptr的的原理非常粗暴----既然拷贝和赋值是个坑那我直接禁用你。。。 因此在unique_ptr中其主要改变就是禁用了拷贝和赋值。 禁用拷贝和赋值有下面几种方法
成员函数私有化。只声明不定义用delete关键字修饰。
下面是unique_ptr的实现
templateclass Tclass unique_ptr{public:unique_ptr(T* ptr):_ptr(ptr){}unique_ptr(unique_ptr s) delete;unique_ptrT 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;};但是在有些场景下两个对象管理同一块空间的这种需求还是有的因此C11中还出现了更加靠谱的shared_ptr。 其原理就是增加了引用计数。对于同一块资源每赋值或拷贝一次计数就加1 。每析构一个对象就减一。直到为0也就是只有一个对象在维护着这块空间时其就释放这块资源。
下面是shared_ptr的实现 templateclass Tclass 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_ptrT 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也是有一些问题的。
线程安全问题----我们在后面的文章会进行讲解。循环引用问题和对象的删除问题
接下来我们讲 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的实现代码
templateclass Tclass weak_ptr{public:weak_ptr():_ptr(nullptr){}weak_ptr(const shared_ptrT s):_ptr(s.get()){}weak_ptrT operator(const shared_ptrT 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[]释放的对象时便会发生错误。
我们也不能去修改库里的析构函数。那我们该怎么解决呢
C11中给我们提供了在构造智能指针对象时可以传一个删除器的对象来应对不同方式申请的对象。 下面我们用仿函数的形式进行模拟 代买实现如下
templateclass T,class Dclass 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_ptrT,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;};删除器templateclass Tclass Free{public:void operator()(T* ptr){cout Free endl;free(ptr);}};templateclass Tclass Delete{public:void operator()(T* ptr){cout Delete endl;delete ptr;}};templateclass Tclass Delete_{public:void operator()(T* ptr){cout Delete_ endl;delete [] ptr;}};