• C++--智能指针--1123


    1.智能指针解决的问题

    1. int div()
    2. {
    3. int a, b;
    4. cin >> a >> b;
    5. if (b == 0)
    6. throw invalid_argument("除0错误");
    7. return a / b;
    8. }
    9. void Func()
    10. {
    11. // 1、如果p1这里new 抛异常会导致p1不会Delete而导致内存泄漏
    12. // 2、如果p2这里new 抛异常会导致p1和p2都不会delete而导致内存泄漏
    13. // 3、如果div调用这里又会抛异常会导致p1和p2都不会delete而导致内存泄漏
    14. int* p1 = new int;
    15. int* p2 = new int;
    16. cout << div() << endl;
    17. delete p1;
    18. delete p2;
    19. }
    20. int main()
    21. {
    22. try
    23. {
    24. Func();
    25. }
    26. catch (exception& e)
    27. {
    28. cout << e.what() << endl;
    29. }
    30. return 0;
    31. }

    综上,我们需要一种可以自动回收内存空间的指针。如果我们把指针套在一种类内,类在析构时会带着指针申请的空间一起析构掉。

    1. class A
    2. {
    3. public:
    4. ~A()
    5. {
    6. cout << "~A()" << endl;
    7. }
    8. //private:
    9. int _a1 = 0;
    10. int _a2 = 0;
    11. };

    2.智能指针的使用及原理

    2.1RAII

    RAII(Resource Acquisition Is Initialization)直译:在初始化的时候获取资源。即在对象构造的时候获取资源,在对象的生命周期之中,控制对资源的访问,最后在对象析构的时候释放资源。

    好处:

    • 不用显示的释放资源(delete)。
    • 对象所需的资源,在对象生命周期之内始终有效。

     模拟实现1

    1. template<class T>
    2. class SmartPtr {
    3. public:
    4. SmartPtr(T* ptr = nullptr)
    5. : _ptr(ptr)
    6. {}
    7. ~SmartPtr()
    8. {
    9. if (_ptr)
    10. {
    11. cout << "Delete:" << _ptr << endl;
    12. delete _ptr;
    13. }
    14. }
    15. private:
    16. T* _ptr;
    17. };

     2.2 原理

    上述还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可 以通过->去访问所指空间中的内容,因此:模板类中还得需要将* 、->重载下,才可让其 像指针一样去使用。 

    1. T& operator*()
    2. {
    3. return *_ptr;
    4. }
    5. T* operator->()
    6. {
    7. return _ptr;
    8. }

     智能指针的思想比较简单,但如何拷贝构造是个问题

     

     这里程序崩溃了 原因依然是之前的老问题,对同一块空间进行了两次析构。(别试,崩溃了)

    那我们要跟之前一样实现深拷贝吗?不能,因为我们要的就是浅拷贝,要的就是用新的指针来指向我这块资源。

    下面我们来看一下C++是如何对这里进行处理。

    2.3 auto_ptr

    auto_ptrsp2(sp1)

    C98里面的大槽点,对拷贝构造的处理沿用了右值引用的资源转移。(可右值本来就是将亡值,给了就给了。左值可不是啊,如果我还需要对sp1进行访问,那就会报访问空指针的错误)。我们将auto_ptr模拟实现一下

    1. namespace chy
    2. {
    3. template<class T>
    4. class auto_ptr
    5. {
    6. public:
    7. //用指针来构造的
    8. auto_ptr(T* ptr = nullptr)
    9. :_ptr(ptr)
    10. {}
    11. //C98中的auto_ptr对于拷贝的问题处理有问题,相当于右值的资源转移
    12. //会把ap里面的资源转移给构造出来的智能指针,而ap将不在管理这些资源(置空)
    13. //所以如果我们再次对与于ap进行解引用操作,将会出现访问空指针的问题
    14. auto_ptr(auto_ptr& ap)
    15. :_ptr(ap._ptr)
    16. {
    17. ap._ptr = nullptr;
    18. }
    19. auto_ptr& operator=(const auto_ptr& ap)
    20. {
    21. if (this != &ap)//如果不是自己给自己赋值
    22. {
    23. if (_ptr)//原本的资源不管了(删除)
    24. {
    25. cout << "Delete" << _ptr << endl;
    26. delete _ptr;
    27. }
    28. _ptr = ap._ptr;
    29. ap._ptr = nullptr;
    30. }
    31. return *this;
    32. }
    33. ~auto_ptr()
    34. {
    35. if (_ptr)
    36. {
    37. cout << "Delete:" << _ptr << endl;
    38. delete _ptr;
    39. }
    40. }
    41. T& operator*()
    42. {
    43. return *_ptr;
    44. }
    45. T* operator->()
    46. {
    47. return _ptr;// &(*_ptr)
    48. }
    49. private:
    50. T* _ptr;
    51. };
    52. }

     拷贝是没问题的 但是不能对sp1再进行解引用操作了

     3. C++11的智能指针

     3.1 unique_ptr

    遇到问题,逃避问题

    不让拷贝

    unique_ptr(const unique_ptr& ap) = delete;
    unique_ptr& operator=(unique_ptr& ap) = delete;

    1. template<class T>
    2. class unique_ptr
    3. {
    4. public:
    5. unique_ptr(T* ptr=nullptr)
    6. :_ptr(ptr)
    7. {}
    8. unique_ptr(const unique_ptr& ap) = delete;
    9. unique_ptr& operator=(unique_ptr& ap) = delete;
    10. ~unique_ptr()
    11. {
    12. if (_ptr)
    13. {
    14. cout << "Delete:" << _ptr << endl;
    15. delete _ptr;
    16. }
    17. }
    18. T& operator*()
    19. {
    20. return *_ptr;
    21. }
    22. T* operator->()
    23. {
    24. return _ptr;
    25. }
    26. private:
    27. T* _ptr;
    28. };

    3.2 share_ptr

    引用计数,每有一个指针指向我这块区域,引用计数++,当最后一个指向改位置的智能指针析构时,带着区域一起走。

    我这个计数怎么设置?

    1. {
    2. //..
    3. private:
    4. static int count;
    5. //..
    6. }
    7. template<class T>
    8. int shared_ptr::_count = 0;

    这样设计可以解决多个指针指向同一块空间的问题。可如果我有两块空间呢?这两块空间的引用计数是不是就变成了同一个了?

    所以我们不能纳入静态成员变量,同时我们注意到,我们每指向一块空间,就需要一个单独的计数。那我们在指向空间的时候构造计数不就好了?

    而且,由于多个指针共享一个计数,理所应当的成为临界资源,在访问修改时需要加锁!

    于是我们在构造函数中下笔

    1. { //...
    2. shared_ptr(T* sp = nullptr)
    3. : _ptr(sp)
    4. , _pCount(new int(1))
    5. ,_pmtx(new std::mutex)
    6. {}
    7. private:
    8. T* _ptr;
    9. int* _pCount;
    10. std::mutex* _pmtx;
    11. }

    整体实现

    1. template<class T>
    2. class shared_ptr
    3. {
    4. public:
    5. shared_ptr(T* sp=nullptr)
    6. :_ptr(sp)
    7. ,_pCount(new int(1))
    8. ,_pmtx(new std::mutex)
    9. {}
    10. shared_ptr(const shared_ptr& sp)
    11. :_ptr(sp._ptr)
    12. ,_pCount(sp._pCount)
    13. ,_pmtx(sp._pmtx)
    14. {
    15. AddCount();
    16. }
    17. shared_ptr& operator=(const shared_ptr& sp)
    18. {
    19. if (this == &sp) return *this;
    20. Release();
    21. _ptr = sp._ptr;
    22. _pCount = sp._pCount;
    23. _pmtx = sp._pmtx;
    24. AddCount();
    25. return *this;
    26. }
    27. void AddCount()
    28. {
    29. _pmtx->lock();
    30. ++(*_pCount);
    31. _pmtx->unlock();
    32. }
    33. void Release()
    34. {
    35. _pmtx->lock();
    36. bool flag = false;
    37. if (--(*_pCount) == 0 && _ptr)
    38. {
    39. cout << "Delete: " << _ptr << endl;
    40. delete _ptr;
    41. delete _pCount;
    42. flag = true;
    43. }
    44. _pmtx->unlock();
    45. if (flag) delete _pmtx;
    46. }
    47. ~shared_ptr()
    48. {
    49. Release();
    50. }
    51. T& operator*()
    52. {
    53. return *_ptr;
    54. }
    55. T* operator->()
    56. {
    57. return _ptr;
    58. }
    59. //这个接口是给weak_ptr准备的 用来拿到指针
    60. T* get() const
    61. {
    62. return _ptr;
    63. }
    64. private:
    65. T* _ptr;
    66. int* _pCount = 0;
    67. std::mutex* _pmtx;
    68. };

     3.2.2 shared_ptr的循环引用问题

     

     函数结束时,n2先析构,然后n1析构。

    _next管着右边的节点。 _prev管着左边的节点

    _next析构,右边节点就delete。 _prev析构,左边节点就delete

    那_next什么时候析构呢?左边的节点被delete,调用析构函数,其成员函数_next就析构了。

    那左边的节点什么时候被delete呢? _prev析构的时候,左边的节点析构。

    那_prev什么时候析构呢?右边的节点被delete,调用析构函数,其成员函数_prev就析构了。

    那左边的节点什么时候被delete呢?........(开始套娃)

    3.3 weak_ptr

    weak_ptr不是常规的智能指针,没有RAII,不支持直接管理资源。weak_ptr主要用shared_ptr构造,用来解决shared_ptr循环引用问题。

    在shared_ptr中新增一个函数,用来返回指针

    1. template<class T>
    2. class shared_ptr
    3. {
    4. public:
    5. //这个接口是给weak_ptr准备的 用来拿到指针
    6. T* get() const
    7. {
    8. return _ptr;
    9. }
    10. private:
    11. T* _ptr;
    12. int* _pCount = 0;
    13. std::mutex* _pmtx;
    14. };

    weak_ptr的简单实现

    1. template<class T>
    2. class weak_ptr
    3. {
    4. public:
    5. weak_ptr()
    6. :_ptr(nullptr)
    7. {}
    8. weak_ptr(const shared_ptr& sp)
    9. :_ptr(sp.get())
    10. {}
    11. weak_ptr(const weak_ptr& wp)
    12. :_ptr(wp._ptr)
    13. {}
    14. weak_ptr& operator=(const shared_ptr& sp)
    15. {
    16. _ptr = sp.get();
    17. return *this;
    18. }
    19. T& operator*()
    20. {
    21. return *_ptr;
    22. }
    23. T* operator->()
    24. {
    25. return _ptr;
    26. }
    27. private:
    28. T* _ptr;
    29. };
  • 相关阅读:
    MySQL-数据库优化策略概述
    华为OD 社招(Java后端)一面
    飞天使-学以致用-devops知识点4-SpringBoot项目CICD实现(实验失败,了解大概流程)
    Kafka的文件存储与稀疏索引机制
    Django之Cookie
    Django框架基础
    EPICS记录参考--Histogram记录(histogram)
    github 本地仓库上传及报错处理
    强烈推荐 20.7k Star!企业级商城开源项目强烈推荐!基于DDD领域驱动设计模型,助您快速掌握技术奥秘,实现业务快速增长
    Cesium快速上手2-Model图元使用讲解
  • 原文地址:https://blog.csdn.net/qq_68741368/article/details/128044626