• c++智能指针(raii)


    目录

    1.智能指针的作用

    2.智能指针带来的问题与挑战

     3.三种不同的智能指针

    4.auto_ptr

    5.unique_ptr

    6.shared_ptr

    7.weak_ptr;相互引用

    8.总结


    1.智能指针的作用

    以c++的异常处理为例看看throw catch用法。有时,一个用new开出的空间用完还没delete呢。突然throw一个错误,程序就跑没了,造成了内存泄漏。为了解决这个问题,引入了智能指针的概念,作用是为了让这块空间可以自动释放。智能指针实际就是用一个类来管理这块空间,利用析构函数来释放空间。同时利用重载-> = *来实现一般指针相同的功能。 


    2.智能指针带来的问题与挑战

    因为是用一个类来进行管理,那么拷贝怎么办?我们都知道普通指针是采用的浅拷贝的方式,智能指针也得有普通指针的功能啊。但是,若是简单的完成浅拷贝就必然会导致同一块空间多次释放的问题。看图:

    ap1和ap2都指向了同一块资源,俩再一析构,就全完了。


     3.三种不同的智能指针

    a.auto_ptr:直接转移管理权(摆烂做法,不推荐)。

    b.unique_ptr:直接不让你拷贝,拷贝就报错。(太粗暴,没有解决问题,也不推荐)。

    c.shared_ptr:用引用计数的方式解决了这个问题。(推荐,完美解决问题)。


    4.auto_ptr

    上菜:

    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. class A
    6. {
    7. public:
    8. ~A()
    9. {
    10. cout << "~A true" << endl; //验证析构次数
    11. }
    12. int _a=0; //为了下面验证才搞成public的
    13. };
    14. namespace cpp
    15. {
    16. template<class T>
    17. class auto_ptr
    18. {
    19. private:
    20. T* _ptr;
    21. public:
    22. auto_ptr(T* ptr=nullptr)
    23. :_ptr(ptr)
    24. {}
    25. auto_ptr(auto_ptr& ap) //拷贝构造
    26. :_ptr(ap._ptr)
    27. {
    28. ap._ptr = nullptr;
    29. //auto_ptr用了非常搞的方式来拷贝,直接将资源管理权给拷贝对象,我摆烂辣!
    30. //被拷贝的对象直接置空,防止2次析构。
    31. }
    32. ~auto_ptr()
    33. {
    34. if (_ptr) //非空删除空间
    35. {
    36. delete _ptr;
    37. _ptr = nullptr;
    38. }
    39. }
    40. auto_ptr& operator=(auto_ptr& ap)
    41. {
    42. if (&ap != this) //要考虑自身赋值的问题
    43. {
    44. //与拷贝构造不同的是,先得把自己释放了,才可以拿别人的,防止内存泄漏。
    45. if (_ptr)
    46. {
    47. delete _ptr;
    48. }
    49. _ptr = ap._ptr;
    50. ap._ptr = nullptr;
    51. }
    52. return *this;
    53. }
    54. T* operator->()
    55. {
    56. return _ptr;
    57. }
    58. T& operator*()
    59. {
    60. return *_ptr;
    61. }
    62. };
    63. }

    再来看看测试:

    1. int main()
    2. {
    3. //看看能不能和普通指针一样用
    4. cpp::auto_ptr ap1 = new(A);

     要是再调用ap1就崩溃,这auto_ptr不太行。


    5.unique_ptr

    上菜:

    1. template<class T>
    2. class unique_ptr
    3. {
    4. private:
    5. T* _ptr;
    6. public:
    7. unique_ptr(T* ptr)
    8. :_prt = ptr;
    9. {}
    10. unique_ptr(unique_ptr& up)=delete; //用delete关键字不让你用
    11. unique_ptr& operator=(unique_ptr& up) = delete;
    12. ~unique_ptr()
    13. {
    14. if (_ptr) //非空删除空间
    15. {
    16. delete _ptr;
    17. _ptr = nullptr;
    18. }
    19. }
    20. T* operator->()
    21. {
    22. return _ptr;
    23. }
    24. T& operator*()
    25. {
    26. return *_ptr;
    27. }
    28. };

    这都没啥好试的,直接拷贝用不了。


    6.shared_ptr

    这么巧妙的shared_ptr得好好说说,我们想到计数引用,估计都会先想到用一个静态(static)的count来计数。但这是搞不定的,每个类都搞一个,无法共享count。所以,我们采用一个共享的空间来存储count。

    1. template<class T>
    2. class shared_ptr
    3. {
    4. private:
    5. T* _ptr;
    6. int* _pCount; //用一块共享空间计数
    7. public:
    8. shared_ptr(T* ptr=nullptr)
    9. :_ptr(ptr)
    10. ,_pCount(new int(1)) //默认构造给1
    11. {}
    12. shared_ptr(shared_ptr& sp)
    13. :_ptr(sp._ptr)
    14. ,_pCount(sp._pCount)
    15. {
    16. ++(*_pCount); //拷贝构造完成记得++count
    17. }
    18. shared_ptr& operator=(shared_ptr& sp)
    19. {
    20. if (&sp != this) //首先保证不是自己拷贝自己。
    21. {
    22. if (--(*sp._pCount) == 0) //当*pCount == 1时,要释放空间。
    23. {
    24. delete sp._ptr;
    25. delete sp._pCount;
    26. }
    27. _ptr = sp._ptr;
    28. _pCount = sp._pCount;
    29. ++(*_pCount);
    30. }
    31. return *this;
    32. }
    33. ~shared_ptr()
    34. {
    35. if ((--*_pCount) == 0)
    36. {
    37. delete _ptr;
    38. delete _pCount;
    39. }
    40. }
    41. T* operator->()
    42. {
    43. return _ptr;
    44. }
    45. T& operator*()
    46. {
    47. return *_ptr;
    48. }
    49. };
    50. }

    看看测试:

    1. int main()
    2. {
    3. cpp::shared_ptr ap1 = new(A);

     完美,以后再也不用担心漏掉delete啦,用完自动就释放。


    7.weak_ptr;相互引用

    1. class Node
    2. {
    3. public:
    4. ~Node()
    5. {
    6. cout << "~Node true" << endl; //验证析构次数
    7. }
    8. int _val = 0;
    9. shared_ptr _next = nullptr; //必须用智能指针才能对应上类型
    10. shared_ptr _prev = nullptr;
    11. };
    12. int main()
    13. {
    14. shared_ptrsp1 (new(Node));
    15. shared_ptrsp2 (new(Node));
    16. //形成了相互引用
    17. sp1->_next = sp2;
    18. sp2->_prev = sp1;
    19. cout << sp1.use_count() << endl; //use_count表示引用计数的大小
    20. cout << sp2.use_count() << endl;
    21. }

     我注释掉一行,结果没问题。

     我去掉注释

    就析构不了。

    这就是引用计数的缺点,如图:

     两边count都变成了1,就僵住了。左边整个空间想要释放需要右边空间的_prev释放;右边空间的_prev释放需要右边整个空间释放;右边整个空间想要释放需要左边空间的_next释放;左边空间的_next释放需要左边整个空间释放。走边整个空间的释放......(串上了,没完没了了,谁也释放不了)。

    这时,我们需要用弱化指针weak_ptr,直接让_next和_prev无法改变空间的引用计数。

    shared_ptr是可以接收weak_ptr的。

    看图查看差别:

    ok,问题解决,直接让他俩count都是1。 


    8.总结

    不得不说,这c++为了填上没垃圾回收器的坑,搞了一套这么复杂的智能指针出来。有价值的就是shared_ptr啦。智能指针也会带来问题(相互引用)又得用weak_ptr来解决。不过,学会这套机制对于我们理解内存管理有很大的帮助。

  • 相关阅读:
    Grafana系列-统一展示-7-ElasticSearch数据源
    redis实现缓存集合不需要查询数据库了
    Java中的队列Queue
    linux rsyslog综合实战2
    万应低代码11月重点更新内容速递
    实验(二):单片机数据区传送程序设计
    数据库优化redis【培训结语】
    SpringMVC中@RequestParam和@RequestBody有什么区别呢?
    参考基因组下载 hg19 索引文件 grch38 reference genome infercnv 安装cellranger
    VOCALOID笔记
  • 原文地址:https://blog.csdn.net/xiao_xiao21/article/details/128014321