> 技术文档 > 【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)


小编个人主页详情<—请点击
小编个人gitee代码仓库<—请点击
c++系列专栏<—请点击
倘若命中无此运,孤身亦可登昆仑,送给屏幕面前的读者朋友们和小编自己!
在这里插入图片描述


目录

    • 前言
    • 一、为什么需要智能指针
    • 二、智能指针
      • RAII
      • 像指针一样使用
    • 三、auto_ptr
      • 概念讲解
      • 模拟实现
        • 测试
    • 四、unique_ptr
      • 概念讲解
      • 模拟实现
        • 测试
    • 五、shared_ptr
      • 概念讲解
      • 模拟实现
        • 测试
    • 六、shared_ptr的循环引用
      • 场景引入
      • 如何解决循环引用问题
    • 七、weak_ptr
      • 概念讲解
      • 模拟实现
        • 测试
    • 八、shared_ptr的定制删除器
      • 概念讲解
      • 模拟实现
        • 测试一
        • 测试二
        • 测试三
        • 测试四
    • 九、智能指针的总结
    • 十、内存泄漏
      • 概念讲解
      • 内存泄漏分类
      • 如何检测内存泄漏
      • 如何避免内存泄漏
    • 十一、源代码
      • SmartPtr.h
      • Test.cpp
    • 总结

前言

【c++】c++11新特性(function包装器,bind包装器)
——书接上文 详情请点击<——
本文由小编为大家介绍——【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)


一、为什么需要智能指针

  1. 那么我们先看一下如下代码,分析一下下面代码存在什么问题
#define _CRT_SECURE_NO_WARNINGS 1#include #include using namespace std;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;div();delete p1;delete p2;}int main(){try{Func();}catch (exception& e){cout << e.what() << endl;}return 0;}
  1. 我们知道使用new,如果使用new申请的空间很大很大,那么new是很有可能申请不出来的,此时new就会抛异常
  2. 那么以fun函数中的p1,p2(new抛异常)以及div(除零错误)中都有可能抛异常,此时小编就来带领大家解析一下
  3. p1使用new申请空间,如果申请空间失败了,那么就不需要进行释放,此时跳转执行流到main函数中的catch
  4. p2使用new申请空间,如果失败了,如果采用上面的写法,此时跳转执行流到main函数中的catch,那么p1就没有进行释放,就会造成内存泄漏
  5. div如果发生除零错误,此时跳转执行流到main函数中的catch,那么p1,p2就没有进行释放,就会造成内存泄漏,c++中有异常机制可以进行捕捉异常, 在throw之前进行释放内存,使用try…catch机制重新编写一下fun,小编已经编写好了,如下
void Func(){try{int* p1 = new int;try{int* p2 = new int;try{div();cout << \"正常释放p1,p2\" << endl;delete p1;delete p2;}catch (...){cout << \"div抛异常,释放p2,\";delete p2;throw;}}catch(...){cout << \"p1\" << endl;delete p1;throw;}}catch(...){throw;}}

运行结果如下
【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

  1. 这种方式虽然可以解决问题,但是太过繁琐,需要层层嵌套try…catch进行捕获异常,释放,抛异常,上面小编才仅仅使用了两个new申请空间,定义对象,层层嵌套try…catch已经如此麻烦,以后如果有场景需要写10个new,50个new,乃至100个new呢?我们都需要进行如此嵌套吗?那么也未必太过繁琐与复杂了吧,此时我们就可以使用智能指针进行管理资源

二、智能指针

智能指针应该包括以下两大特性:

  1. RAII
  2. 像指针一样使用

RAII

RAII是一种利用对象的生命周期控制程序资源的简单技术
在对象构造时获取资源,在对象生命周期结束时析构去释放资源

  1. 那么此时我们就不再需要显示的手动释放资源了
  2. 对象所管理的资源,在对象的生命周期内始终有效
class SmartPtr{public:SmartPtr(int* ptr):_ptr(ptr){}~SmartPtr(){cout << \"delete: \" << _ptr << endl;delete _ptr;}private:int* _ptr;};
  1. 那么我们就可以使用SmartPtr实例化的对象去管理资源,去解决需要try…catch层层嵌套的问题
int div(){int a, b;cin >> a >> b;if (b == 0)throw invalid_argument(\"除0错误\");return a / b;}void Func(){SmartPtr sp1(new int);SmartPtr sp2(new int);div();}int main(){try{Func();}catch (exception& e){cout << e.what() << endl;}return 0;}

运行结果如下
【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

  1. 我们实际上更应该将RAII定义为类模板,这样面对多种类型的资源包括自定义类型的资源都可以进行管理,如下
template<class T>class SmartPtr{public:SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){cout << \"delete: \" << _ptr << endl;delete _ptr;}private:T* _ptr;};
  1. 那么小编定义出一个类A,使用SmartPtr实例化的对象去进行管理
//这里的类A小编后面需要多次使用struct A{int _a;A(int a = 0):_a(a){cout << \"A(int a = 0)\" << endl;}~A(){cout << \"~A()\" << endl;}};int main(){SmartPtr<A> sp1(new A(1));SmartPtr<A> sp2(new A(2));return 0;}

运行结果如下
【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

像指针一样使用

仅仅是上面的SmartPtr还不能够称为智能指针,智能指针还应该具有指针的特性,指针可以解引用*,指针也可以使用箭头->,类似于迭代器,去重载*和->,那么也就是小编定义的智能指针还应该重载*和->

template<class T>class SmartPtr{public:SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){cout << \"delete: \" << this << endl;delete _ptr;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;//直接返回成员变量_ptr(指针)即可}private:T* _ptr;};
  1. 那么小编接下来分别演示一下智能指针重载的解引用*和箭头->的使用
int main(){SmartPtr<int> sp1(new int(1));cout << *sp1 << endl;SmartPtr<pair<string, string>> sp2(new pair<string, string>(\"xxx\", \"yyy\"));cout << sp2->first << \':\' << sp2->second << endl;return 0;}

运行结果如下
【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

对于auto_ptr, unique_ptr, shared_ptr, weak_ptr这四个智能指针的模拟实现,由于命名上会和库里的会有冲突,所以小编将auto_ptr, unique_ptr, shared_ptr, weak_ptr的模拟实现放在SmartPtr.h这个头文件中,放在命名空间中

三、auto_ptr

概念讲解

【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

c++98中其实已经有了智能指针,也就是auto_ptr,但是这个auto_ptr可以说是智能指针的败笔

  1. 那么我们看一下库里的auto_ptr是否可以正常管理资源
struct A{int _a;A(int a = 0):_a(a){cout << \"A(int a = 0)\" << endl;}~A(){cout << this << \' \';cout << \"~A()\" << endl;}};int main(){auto_ptr<A> ap1(new A(1));auto_ptr<A> ap2(new A(2));return 0;}

运行结果如下
【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

  1. 我们可以看到,是可以正常进行管理的,那么它的缺陷又是在哪里呢?源自于它的拷贝构造,auto_ptr采用的方式是管理权转移的方式进行拷贝,会造成被拷贝对象悬空问题,形成空指针的访问,导致程序崩溃
int main(){auto_ptr<A> ap1(new A(1));auto_ptr<A> ap2(new A(2));auto_ptr<A> ap3(ap1);ap1->_a++;ap3->_a++;return 0;}

运行结果如下
【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

  1. 上面的运行结果显而易见,程序崩溃了,那么是由于什么原因呢?我们知道智能指针中的成员函数就是一个指针,那么如果仅仅是对指针进行浅拷贝,那么就会有两个智能指针对象去管理一个资源,当着两个对象的生命周期结束的时候,由于这两个对象管理同一个资源,那么这个资源就会被析构并释放两次,对同一块空间释放两次,这无疑会造成崩溃
  2. 那么auto_ptr则采用管理权转移的方式进行处理,将被拷贝对象浅拷贝给拷贝对象,同时将被拷贝对象的指针置空,这样管理资源的对象只有一个了,此时当这一个对象生命周期结束的时候资源就只会释放一次了,但是会造成被拷贝对象的悬空,此时如果对被拷贝对象进行访问,那么由于被拷贝对象所管理的对象是空,也就是说被拷贝对象中的指针指向了空,此时对被拷贝对象进行访问,进行操作,会造成空指针的访问,那么程序的最终结果必然是走向崩溃
    【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)
    【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)
    【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

模拟实现

  1. auto_ptr是智能指针同样要拥有RAII和像指针一样使用的特性
  2. auto_ptr在SmartPtr的基础上新增了拷贝构造
  3. 这个拷贝构造就是实现管理权转移,将被拷贝对象浅拷贝给拷贝对象,同时将被拷贝对象的指针置空即可
template<class T>class auto_ptr{public:auto_ptr(T* ptr):_ptr(ptr){}auto_ptr(auto_ptr<T>& ap):_ptr(ap._ptr){ap._ptr = nullptr;}~auto_ptr(){cout << \"delete: \" << _ptr << endl;delete _ptr;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
测试
  1. 同样的将上面的测试代码拿到下面来,使用小编模拟实现的auto_ptr测试是否可以正常管理对象
#include \"SmartPtr.h\"struct A{int _a;A(int a = 0):_a(a){cout << \"A(int a = 0)\" << endl;}~A(){cout << this << \' \';cout << \"~A()\" << endl;}};int main(){wzx::auto_ptr<A> ap1(new A(1));wzx::auto_ptr<A> ap2(new A(2));return 0;}

运行结果如下,可以正常管理对象
【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

  1. 那么接下来我们调用一下拷贝构造看是否实现了管理权转移
int main(){wzx::auto_ptr<A> ap1(new A(1));wzx::auto_ptr<A> ap2(new A(2));wzx::auto_ptr<A> ap3(ap1);ap1->_a++;ap3->_a++;return 0;}
  1. 调试如下,进行拷贝实现了管理权转移

【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)
【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)
【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)
运行结果如下,对悬空的对象进行访问,那么就会造成对空指针的访问,也就是程序崩溃
【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

四、unique_ptr

【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

概念讲解

c++11中提供了unique_ptr,而unique_ptr是智能指针,所以具有RAII和像指针一样使用的特性,对于拷贝构造,顾名思义,unique_ptr的中文意思是只有一个指针,那么对于拷贝则是简单粗暴,你auto_ptr不是拷贝后被拷贝对象会造成悬空吗?进行访问被拷贝对象会有问题,那我unique_ptr粗暴点好了,不让你去进行对应的拷贝和赋值不就好了,这样也可以保证资源只会被一个对象进行管理,当对象的生命周期结束的时候,资源也就不会释放两次了,资源就只会被释放一次

  1. 那么我们先看一下库里的unique_ptr是否可以防拷贝和防赋值
int main(){unique_ptr<int> up1(new int(1));unique_ptr<int> up2(new int(1));unique_ptr<int> up3(up1);up2 = up1;return 0;}

运行结果如下,进行了防拷贝和放赋值
【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

模拟实现

  1. 对于unique其余实现和SmartPtr相同,只不过对于拷贝构造和赋值运算符重载我们将其使用c++11的delete进行删除这两个函数即可,这样编译器就不会生成默认的拷贝构造和赋值运算符重载了,那么在外部就无法使用已删除的函数了
template<class T>class unique_ptr{public:unique_ptr(T* ptr):_ptr(ptr){}unique_ptr(unique_ptr<T>& ap) = delete;unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;~unique_ptr(){cout << \"delete: \" << _ptr << endl;delete _ptr;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
测试
int main(){wzx::unique_ptr<int> up1(new int(1));wzx::unique_ptr<int> up2(new int(1));wzx::unique_ptr<int> up3(up1);up2 = up1;return 0;}

运行结果如下,小编模拟实现的unique_ptr进行了防拷贝和防赋值,通常进行防拷贝的地方都要进行防赋值操作
【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

五、shared_ptr

【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

概念讲解

c++11中提供了支持拷贝的智能指针shared_ptr
shared_ptr的原理:通过引用计数的方式实现多个对象之间共享管理资源

  1. shared_ptr在其内部,给每个资源都维护着一份计数,用来记录该资源被几个对象管理
  2. 当对象生命周期结束,也就是对象调用析构函数,就说明对象管理资源了,此时对象的引用计数减一
  3. 如果引用计数减一后为0,说明当前对象已经是最后一个使用该资源的对象了,此时必须释放该资源
  4. 如果引用计数减一后不为0,说明除了当前对象外还有其它对象在管理该资源,那么就不需要进行释放该资源,如果释放了该资源,那么对那块空间的权限就归还给了操作系统,此时对于其它对象如果访问资源,即那块空间就会形成野指针的访问,会造成程序崩溃
  5. 资源始终由最后一个使用该资源的对象进行释放
  6. 那么我们首先看一下库中的shared_ptr的共享资源
struct A{int _a;A(int a = 0):_a(a){cout << \"A(int a = 0)\" << endl;}~A(){cout << this << \' \';cout << \"~A()\" << endl;}};int main(){shared_ptr<A> sp1(new A(1));shared_ptr<A> sp2(new A(1));shared_ptr<A> sp3(sp1);sp1->_a++;sp3->_a++;cout << sp1->_a << endl;cout << sp3->_a << endl;return 0;}

运行结果如下
【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

  1. sp3拷贝sp1,sp3和sp1共同管理一份资源A,那么分别使用sp1和sp3访问类A的成员变量_a对其进行加加,经过两次加加,_a变成3,正确,并且虽然有三个智能指针对象,但是其中有两个对象共同管理一个资源,另一个对象管理一个资源,此时正确进行析构,无误

模拟实现

  1. shared_ptr是智能指针,应该具有RAII和像指针一样使用的特性
  2. shared_ptr的关键就是如何采用引用计数,如何给每份资源配一个引用计数,那么也就是说在资源被申请的时候这个计数就也应该一并被申请了
  3. 如果这个计数放到栈上,那么每一个智能指针对象就会有自己的计数,我们想要的是管理相同资源的对象共同维护这个引用计数,所以在栈上定义计数不满足需求
  4. 如果在静态区上定义计数,倒是符合管理相同资源的对象共享计数,但是这个智能指针的管理其它资源的对象也会使用到这个计数,因为这个计数是在静态区的,所以这个引用计数属于shared_ptr这个类,属于这个类实例化出的所有的对象,所有的对象都可以访问到这个引用计数,那么这不就乱套了吗,我们想要的是管理相同资源的对象共同共享计数,这里倒成了属于这个shared_ptr类实例化出的所有的对象都共享了,所以也不满足需求
  5. 最后就只剩下堆了,当对象开始进行管理资源调用构造函数的时候,一并的使用new在堆上申请一块空间作为资源的计数,这样进行拷贝的时候那么此时由对象共同维护资源的计数即可,符合需求,所以对于shared_ptr我们应该新增一个私有成员对象_pcount作为每个资源的引用计数
  6. 那么对于构造函数的时候,由于是资源最初被shared_ptr的对象进行管理,所以此时管理资源的对象只有一个,所以我们在堆上申请空间的时候,初始化引用计数为1即可
template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr):_ptr(ptr),_pcount(new int(1)){}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;int* _pcount;};
  1. 对于析构函数,那么我们使用if语句进行判断即可,如果- -计数后,计数的值为0,那么我们就进行释放资源_ptr和计数_pcount即可
  2. 这里巧妙的利用了if语句的特性,if语句的判断是一定会执行的,所以- -计数操作一定会执行,如果- -之后不为0,那么不会进入if语句中执行释放资源,所以此时就可以保证无论是否需要释放资源,- -计数一定会被执行
~shared_ptr(){if (--(*_pcount) == 0){cout << \"delete\" << _ptr << endl;delete _ptr;delete _pcount;}}
  1. 对于shared_ptr的拷贝构造函数,那么仅需要将被拷贝的对象管理的资源和维护的计数进行浅拷贝即可,这样被拷贝的对象和拷贝的对象就共同管理资源以及维护引用计数
  2. 由于进行拷贝构造之后,多了一个对象管理资源,所以引用计数要加一
shared_ptr(const shared_ptr<T>& sp){_pcount = sp._pcount;_ptr = sp._ptr;(*_pcount)++;}
  1. 那么对于赋值运算符重载,当我们编写赋值运算符重载的时候,首先就应该想到要防止自己给自己赋值的情况,一般是使用this == &sp进行判断,但是这里有点特殊,虽然使用前面的this == &sp可以判断出来自己给自己赋值,但是还有一个情况没有考虑到,那就是管理着相同资源的对象之间进行赋值,那么也不就是变相的自己给自己进行赋值吗?那么我们可以使用对象管理的资源的地址进行判断是否是自己给自己赋值以及管理着相同资源的对象之间进行赋值,如果是那么返回被赋值对象*this即可,一石二鸟
  2. 由于被赋值对象经过赋值后要和赋值对象共同管理资源,那么对于被赋值对象原来管理的资源我们要进行处理,由于被赋值对象经过赋值后要和赋值对象共同管理资源后,那么被赋值对象就不管理它原来管理的资源了,所以要在赋值前对被赋值对象的引用计数进行减一,如果减一之后被赋值对象的引用计数减到了0,那么就说明此时被赋值对象是最后一个管理资源的对象,要对它管理的对象的资源进行释放即可,如果引用计数减一后不为0,那么不需要进行其它处理
  3. 接下来将赋值对象管理的资源和维护的计数进行浅拷贝给被赋值对象即可
  4. 那么进行赋值之后,多了一个对象管理资源,所以引用计数要加一,最后不要忘记返回被赋值对象即可
shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr == sp._ptr){return *this;}if (--(*_pcount) == 0){delete _pcount;delete _ptr;}_pcount = sp._pcount;_ptr = sp._ptr;++(*_pcount);return *this;}
  1. shared_ptr中还要提供两个接口,一个是use_count,用于获取当前对象管理资源对应的引用计数的数量,一个是get,用于获取对象的私有成员变量_ptr
int use_count() const{return *_pcount;}T* get() const{return _ptr;}
测试
  1. 那么小编使用自己模拟实现的shared_ptr看一下是否可以共享资源,并且正常析构
int main(){wzx::shared_ptr<A> sp1(new A(1));wzx::shared_ptr<A> sp2(new A(1));wzx::shared_ptr<A> sp3(sp1);sp2 = sp1;sp1->_a++;sp3->_a++;cout << sp1->_a << endl;cout << sp3->_a << endl;return 0;}
  1. sp2没有赋值前,sp2管理着一份资源,sp1和sp3管理着一份资源

【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

  1. 进行赋值后,sp2由于是最后一个管理那份资源的对象,所以必须要进行析构,sp1和sp2和sp3共同管理着一份资源,此时资源的计数为3

【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

  1. 最后三个对象的生命周期结束,引用计数减到0,调用析构释放三个对象共同管理的一份资源

【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

六、shared_ptr的循环引用

其实shred_ptr有一个特殊场景也就是循环引用会比较坑,会造成内存泄漏的问题

场景引入

  1. 我们使用shared_ptr去管理一个节点,这个节点中有可以指向前面和后面的指针,我们使用shared_ptr去管理这个节点之后,期望将节点链接起来
struct A{int _a;A(int a = 0):_a(a){cout << \"A(int a = 0)\" << endl;}~A(){cout << this << \' \';cout << \"~A()\" << endl;}};struct Node{A _val;Node* _next;Node* _prev;};int main(){shared_ptr<Node> sp1(new Node);shared_ptr<Node> sp2(new Node);sp1->_next = sp2;sp2->_prev = sp1;return 0;}
  1. 此时我们进行链接p1和p2,发现无法链接,原因是sp2是自定义类型,而节点中的_next和_prev则是原生指针,所以类型不匹配无法链接
    【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)
  2. 那么小编为了让类型匹配,干脆就在节点中存储shared_ptr类型的对象,这样类型就匹配了
struct A{int _a;A(int a = 0):_a(a){cout << \"A(int a = 0)\" << endl;}~A(){cout << this << \' \';cout << \"~A()\" << endl;}};struct Node{A _val;shared_ptr<Node> _next;shared_ptr<Node> _prev;};int main(){shared_ptr<Node> sp1(new Node);shared_ptr<Node> sp2(new Node);sp1->_next = sp2;sp2->_prev = sp1;return 0;}

运行结果如下,节点Node中有一个自定义类型A的对象_val,此时sp1和sp2的生命周期结束居然没有调用A的析构函数,不是说好的智能指针shared_ptr可以管理好资源,当对象生命周期结束的时候自动调用资源的析构函数吗?那么这又该如何理解
【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

  1. shared_ptr中有一个接口use_count可以查看当前对象所维护的引用计数的数量,那么我们添加查看一下
struct Node{A _val;shared_ptr<Node> _next;shared_ptr<Node> _prev;};int main(){shared_ptr<Node> sp1(new Node);shared_ptr<Node> sp2(new Node);cout << sp1.use_count() << endl;cout << sp2.use_count() << endl;sp1->_next = sp2;sp2->_prev = sp1;cout << sp1.use_count() << endl;cout << sp2.use_count() << endl;return 0;}

运行结果如下
【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

  1. 在进行sp1和sp2链接前引用计数为1这个可以理解,但是sp1和sp2进行链接后引用计数为2这个又该如何理解呢?如下图
  1. 循环引用解释如下
    【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

如何解决循环引用问题

  1. 那么我们应该如何处理这种场景呢?c++11专门提供了weak_ptr特定的处理shared_ptr中循环引用的问题,使用weak_ptr作为节点中的智能指针即可,weak_ptr不管理资源,不会增加引用计数
struct Node{A _val;weak_ptr<Node> _next;weak_ptr<Node> _prev;};int main(){shared_ptr<Node> sp1(new Node);shared_ptr<Node> sp2(new Node);cout << sp1.use_count() << endl;cout << sp2.use_count() << endl;sp1->_next = sp2;sp2->_prev = sp1;cout << sp1.use_count() << endl;cout << sp2.use_count() << endl;return 0;}

运行结果如下
【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

  1. 此时shared_ptr中循环引用的问题就被解决了,我们对比一下,没有使用weak_ptr的场景是引用计数加一,而使用weak_ptr之后,引用计数没有增加
  2. 此时对应下图,赋值后引用计数仍然为1,那么当sp2的生命周期结束,那么引用计数减一,引用计数减为0,会析构右边的Node,sp1的生命周期结束,那么引用计数减一,引用计数减为0,会析构左边的Node,此时shared_ptr循环引用造成内存泄漏的场景就被weak_ptr完美解决
    【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

七、weak_ptr

【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

概念讲解

weak_ptr同样也是c++11的智能指针,weak_ptr并不用于管理资源,它的出现仅仅是为了解决shared_ptr的循环引用的问题
weak_ptr有可以像指针一样使用的性质,但是不支持RAII

模拟实现

  1. weak_ptr的构造函数要支持一个无参的构造,支持一个使用shared_ptr中的指针进行构造的构造函数,那么由于shared_ptr的指针是私有成员变量,我们无法在类外获取指针_ptr,此时我们可以调用前面小编在shared_ptr中提前铺垫的get用于获取指针,那么此时我们就可以在weak_ptr中使用shared_ptr的get函数获取指针进行构造weak_ptr了
  2. 由于weak_ptr不用于管理资源,不支持RAII,所以weak_ptr没有析构函数,因为类似的,例如循环引用的场景,weak_ptr的资源实际上还是由shared_ptr进行管理
  3. weak_ptr要像指针一样使用,所以还是需要重载*和->
template<class T>class weak_ptr{public:weak_ptr():_ptr(nullptr){}weak_ptr(const shared_ptr<T>& sp):_ptr(sp.get()){}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
测试
  1. 此时我们就使用循环引用的场景,看一下小编模拟实现的weak_ptr是否可以解决循环引用的问题,成功调用A的析构函数
struct A{int _a;A(int a = 0):_a(a){cout << \"A(int a = 0)\" << endl;}~A(){cout << this << \' \';cout << \"~A()\" << endl;}};struct Node{A _val;wzx::weak_ptr<Node> _next;wzx::weak_ptr<Node> _prev;};int main(){wzx::shared_ptr<Node> sp1(new Node);wzx::shared_ptr<Node> sp2(new Node);cout << sp1.use_count() << endl;cout << sp2.use_count() << endl;sp1->_next = sp2;sp2->_prev = sp1;cout << sp1.use_count() << endl;cout << sp2.use_count() << endl;return 0;}

运行结果如下,成功
【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

八、shared_ptr的定制删除器

概念讲解

我们上述的对象是new出来的,但是如果我们的对象采用new[ ]的形式进行开辟的或者是malloc的是或者是fopen打开文件的方式呢?
对应的我们就要使用delete[ ]进行释放,使用free()进行释放,使用fclose关闭文件
那么我们该如何做呢?
那么就是定制删除器使用到了包装器的特性进行巧妙的解决
注意由于使用包装器function,所以我们要包头文件#include

int main(){ //三种无法使用delete的场景wzx::shared_ptr<A> sp1(new A[10]);wzx::shared_ptr<int> sp2((int*)malloc(sizeof(int)));wzx::shared_ptr<FILE> sp3(fopen(\"Test.cpp\", \"r\"));return 0;}

模拟实现

【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

  1. 将删除器添加在了shared_ptr的构造函数的中,没有添加在shared_ptr的类模板中
    【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

  2. 相反unique_ptr也有定制删除器,unique_ptr的定制删除器是添加在了类模板中

  3. 那么小编在这里重点讲解shared_ptr的构造函数的定制删除器

  4. 定制删除器其实就是一个可调用对象,例如仿函数,函数指针,lambda,但是由于我们不确定这个传入的可调用对象的类型是究竟是什么,但是可以知道这个可调用对象的类型返回值一定是void,参数一定是T*的,所以就是用包装器定义出_del即可

function<void(T*)> _del;
  1. 实例化shared_ptr对象的时候第二个实参传入可调用对象,那么在shared_ptr的构造函数增加一个模板参数用于推导第二个实参的类型,定义形参del去初始化私有成员变量_del
template<class D>shared_ptr(T* ptr, D del):_ptr(ptr), _pcount(new int(1)), _del(del){}
  1. 那么在shared_ptr的析构函数中就可以使用这个包装器的对象_del去释放资源了,同样的operaotr=中也涉及释放资源,我们也一并修改了
~shared_ptr(){if (--(*_pcount) == 0){cout << \"delete\" << _ptr << endl;_del(_ptr);delete _pcount;}}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr == sp._ptr){return *this;}if (--(*_pcount) == 0){delete _pcount;_del(_ptr);}_pcount = sp._pcount;_ptr = sp._ptr;++(*_pcount);return *this;}
测试一
  1. 那么此时就可以测试当管理资源是通过new[ ]出来的,此时我们对应就要使用delete[ ]进行释放,这里的可调用对象我们可以仿函数,函数指针,lambda,小编将针对三种场景逐一传参
  2. 那么这里小编就是用一个仿函数传入匿名对象即可
struct A{int _a;A(int a = 0):_a(a){cout << \"A(int a = 0)\" << endl;}~A(){cout << this << \' \';cout << \"~A()\" << endl;}};template<class T>struct DeleteArray{void operator()(T* ptr){delete[] ptr;}};int main(){wzx::shared_ptr<A> sp1(new A[10], DeleteArray<A>());return 0;}

运行结果如下,成功
【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

测试二
  1. 当shared_ptr管理的对象是malloc出来的,那么进行释放对应的就要使用free去进行释放
  2. 这里小编则传入可调用对象中的函数指针
void f(int* a){cout << \"free()\" << endl;free(a);}int main(){wzx::shared_ptr<int> sp2((int*)malloc(sizeof(int)), f);return 0;}

运行结果如下,正确
【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

测试三
  1. 当我们以可读的形式打开文件的时候,当使用完成之后就要使用fclose进行关闭文件,由于文件的打开需要占用内存,如果长期不关闭文件,那么就会造成内存泄漏,所以我们要使用fclose关闭文件
  2. 这里小编则传入可调用对象中的lambda实现
int main(){wzx::shared_ptr<FILE> sp3(fopen(\"Test.cpp\", \"r\"), [](FILE* f) {cout << \"fclose()\" << endl;fclose(f);});return 0;}

运行结果如下,正确
【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

测试四
  1. 其实我们的定制删除器还有问题没有解决,虽然这些特殊场景都被小编使用定制删除器一 一解决了但是最根本的场景,也就是我们原本的new的对象无法进行析构了,如下
int main(){wzx::shared_ptr<A> sp4(new A(1));return 0;}

运行结果如下
【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

  1. 为什么会出现这种状况呢?原因是如果我们不传参给包装器的对象_del之后,那么对于原本写死的析构采用delete,这里采用了包装器的对象_del进行析构,所以我们要让包装器的对象_del默认就是delete才可,所以我们使用小巧简洁的lambda去实现delete之后作为_del的缺省值
function<void(T*)> _del = [](T* t) {delete t;};

那么添加包装器的对象_del缺省值后,此时小编重新运行,运行结果如下,无误
【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)

九、智能指针的总结

  1. 智能指针要求具有RAII和像指针一样的特性
  2. 关于几个智能指针的拷贝问题
  3. auto_ptr拷贝采用的是管理权转移,会导致被拷贝对象悬空,不建议使用
  4. unique_ptr禁止拷贝,简单粗暴,日常不需要进行拷贝的场景,建议使用它
  5. shared_ptr采用引用计数的方式支持拷贝,日常需要进行拷贝的场景,建议使用它。但是要小心构成循环引用,循环引用会造成内存泄漏,在使用的shared_ptr要先学会识别循环引用才可以使用weak_ptr解决shared_ptr的循环引用的问题

十、内存泄漏

概念讲解

内存泄漏:内存泄漏是指因疏忽或者错误造成程序未能释放已经不再使用的内存的情况
内存泄漏并不是指内存在物理空间上的消失,而是指应用程序在分配某段内存后,因设计错误,造成失去了对该段内存的控制的场景,造成内存浪费

  1. 内存泄漏的危害:长期运行的程序(如:操作系统,服务器)出现了内存泄漏,危害很大,会造成程序响应越来越慢,甚至会造成卡死
void MemoryLeaks(){ //内存申请了忘记释放 int* p1 = new int; //异常问题 int* p2 = new int; Func(); //如果Func函数抛异常,那么导致delete p2未执行,即p2没被释放. delete p2;}

内存泄漏分类

  1. 通常在c/c++程序中我们只关心堆内存泄漏(Heap Leak)以及系统资源泄漏
  2. 堆内存是指程序在运行中调用malloc/calloc/realloc/new等在堆上申请出来的内存,使用完成堆内存之后要使用对应的free或者delete进行释放内存空间,如果由于程序设计错误造成这部分内存没有释放,那么以后这部分内存将无法使用,这就是堆内存泄漏
  3. 系统资源泄漏是指程序使用系统分配的资源,如:方套接字,管道,文件后没有使用对应的函数进行释放,造成系统资源浪费,严重时会造成系统性能下降,系统执行不稳定

如何检测内存泄漏

linux下可以使用几款工具进行检测,引用自CSDN的优质创造者CHENG Jian详情请点击<——
windows下可使用第三方的VLD工具进行检测

如何避免内存泄漏

  1. 养成良好的代码规范,申请的空间记得释放
  2. 如果遇到异常后,合理使用智能指针处理问题
  3. 采用RAII或智能指针的思想处理问题
  4. 搭配检测工具检测内存泄漏

十一、源代码

SmartPtr.h

#pragma oncenamespace wzx{template<class T>class auto_ptr{public:auto_ptr(T* ptr):_ptr(ptr){}auto_ptr(auto_ptr<T>& ap):_ptr(ap._ptr){ap._ptr = nullptr;}~auto_ptr(){cout << \"delete: \" << _ptr << endl;delete _ptr;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};template<class T>class unique_ptr{public:unique_ptr(T* ptr):_ptr(ptr){}unique_ptr(unique_ptr<T>& ap) = delete;unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;~unique_ptr(){cout << \"delete: \" << _ptr << endl;delete _ptr;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr):_ptr(ptr),_pcount(new int(1)){}template<class D>shared_ptr(T* ptr, D del):_ptr(ptr), _pcount(new int(1)), _del(del){}~shared_ptr(){if (--(*_pcount) == 0){cout << \"delete\" << _ptr << endl;_del(_ptr);delete _pcount;}}shared_ptr(const shared_ptr<T>& sp){_pcount = sp._pcount;_ptr = sp._ptr;(*_pcount)++;}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr == sp._ptr){return *this;}if (--(*_pcount) == 0){delete _pcount;_del(_ptr);}_pcount = sp._pcount;_ptr = sp._ptr;++(*_pcount);return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}int use_count() const{return *_pcount;}T* get() const{return _ptr;}private:T* _ptr;int* _pcount;function<void(T*)> _del = [](T* t) {delete t;};};template<class T>class weak_ptr{public:weak_ptr():_ptr(nullptr){}weak_ptr(const shared_ptr<T>& sp):_ptr(sp.get()){}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};}

Test.cpp

#define _CRT_SECURE_NO_WARNINGS 1#include #include using namespace std;//int div()//{//int a, b;//cin >> a >> b;//if (b == 0)//throw invalid_argument(\"除0错误\");////return a / b;//}////void Func()//{//try//{//int* p1 = new int;////try//{//int* p2 = new int;////try//{//div();////cout << \"正常释放p1,p2\" << endl;//delete p1;//delete p2;//}//catch (...)//{//cout << \"div抛异常,释放p2,\";//delete p2;////throw;//}////}//catch(...)//{//cout << \"p1\" << endl;//delete p1;////throw;//}//}//catch(...)//{//throw;//}//}////int main()//{//try//{//Func();//}//catch (exception& e)//{//cout << e.what() << endl;//}////return 0;//}//class SmartPtr//{//public://SmartPtr(int* ptr)//:_ptr(ptr)//{}////~SmartPtr()//{//cout << \"delete: \" << _ptr << endl;//delete _ptr;//}////private://int* _ptr;//};////int div()//{//int a, b;//cin >> a >> b;//if (b == 0)//throw invalid_argument(\"除0错误\");////return a / b;//}////void Func()//{//SmartPtr sp1(new int);//SmartPtr sp2(new int);////div();//}////int main()//{//try//{//Func();//}//catch (exception& e)//{//cout << e.what() << endl;//}////return 0;//}//template//class SmartPtr//{//public://SmartPtr(T* ptr)//:_ptr(ptr)//{}////~SmartPtr()//{//cout << \"delete: \" << _ptr << endl;//delete _ptr;//}////private://T* _ptr;//};//struct A//{//int _a;////A(int a = 0)//:_a(a)//{//cout << \"A(int a = 0)\" << endl;//}////~A()//{//cout << \"~A()\" << endl;//}//};//int main()//{//SmartPtr sp1(new A(1));//SmartPtr sp2(new A(2));//////return 0;//}//template//class SmartPtr//{//public://SmartPtr(T* ptr)//:_ptr(ptr)//{}////~SmartPtr()//{//cout << \"delete: \" << this << endl;//delete _ptr;//}////T& operator*()//{//return *_ptr;//}////T* operator->()//{//return _ptr;//}////private://T* _ptr;//};////int main()//{//SmartPtr sp1(new int(1));//cout << *sp1 << endl;////SmartPtr<pair> sp2(new pair(\"xxx\", \"yyy\"));//cout <first << \':\' <second << endl;////return 0;//}#include \"SmartPtr.h\"//struct A//{//int _a;////A(int a = 0)//:_a(a)//{//cout << \"A(int a = 0)\" << endl;//}////~A()//{//cout << this << \' \';//cout << \"~A()\" << endl;//}//};//int main()//{//wzx::auto_ptr ap1(new A(1));//wzx::auto_ptr ap2(new A(2));//wzx::auto_ptr ap3(ap1);////ap1->_a++;//ap3->_a++;////return 0;//}//int main()//{//wzx::unique_ptr up1(new int(1));//wzx::unique_ptr up2(new int(1));//wzx::unique_ptr up3(up1);//up2 = up1;////return 0;//}struct A{int _a;A(int a = 0):_a(a){cout << \"A(int a = 0)\" << endl;}~A(){cout << this << \' \';cout << \"~A()\" << endl;}};//int main()//{//wzx::shared_ptr sp1(new A(1));//wzx::shared_ptr sp2(new A(1));//wzx::shared_ptr sp3(sp1);////sp2 = sp1;////sp1->_a++;//sp3->_a++;////cout <_a << endl;//cout <_a << endl;////return 0;//}//struct Node//{//A _val;////Node* _next;//Node* _prev;//};////int main()//{//shared_ptr sp1(new Node);//shared_ptr sp2(new Node);////sp1->_next = sp2;//sp2->_prev = sp1;//////return 0;//}//struct Node//{//A _val;////shared_ptr _next;//shared_ptr _prev;//};////int main()//{//shared_ptr sp1(new Node);//shared_ptr sp2(new Node);////sp1->_next = sp2;//sp2->_prev = sp1;////return 0;//}//struct Node//{//A _val;////weak_ptr _next;//weak_ptr _prev;//};////int main()//{//shared_ptr sp1(new Node);//shared_ptr sp2(new Node);////cout << sp1.use_count() << endl;//cout << sp2.use_count() << endl;////sp1->_next = sp2;//sp2->_prev = sp1;////cout << sp1.use_count() << endl;//cout << sp2.use_count() << endl;////return 0;//}//struct Node//{//A _val;////wzx::weak_ptr _next;//wzx::weak_ptr _prev;//};////int main()//{//wzx::shared_ptr sp1(new Node);//wzx::shared_ptr sp2(new Node);////cout << sp1.use_count() << endl;//cout << sp2.use_count() << endl;////sp1->_next = sp2;//sp2->_prev = sp1;////cout << sp1.use_count() << endl;//cout << sp2.use_count() << endl;////return 0;//}//int main()//{//wzx::shared_ptr sp1(new A[10]);//wzx::shared_ptr sp2((char*)malloc(100));//wzx::shared_ptr sp3(fopen(\"Test.cpp\", \"r\"));//////return 0;//}//定制删除器template<class T>struct DeleteArray{void operator()(T* ptr){delete[] ptr;}};void f(int* a){cout << \"free()\" << endl;free(a);}//int main()//{//wzx::shared_ptr sp2((int*)malloc(sizeof(int)), f);////return 0;//}//int main()//{//wzx::shared_ptr sp3(fopen(\"Test.cpp\", \"r\"), [](FILE* f) {//cout << \"fclose()\" << endl;//fclose(f);//});////return 0;//}//int main()//{//wzx::shared_ptr sp4(new A(1));////return 0;//}//int main()//{////wzx::shared_ptr sp1(new A[10], DeleteArray());//wzx::shared_ptr sp2((int*)malloc(sizeof(int)), f);////wzx::shared_ptr sp3(fopen(\"Test.cpp\", \"r\"), [](FILE* f) {////cout << \"fclose()\" << endl;////fclose(f);////});//////wzx::shared_ptr sp4(new A(1));////return 0;//}

总结

以上就是今天的博客内容啦,希望对读者朋友们有帮助
水滴石穿,坚持就是胜利,读者朋友们可以点个关注
点赞收藏加关注,找到小编不迷路!