• C++智能指针


    1.为什么需要智能指针

    在某些场景下,比如在抛异常的场景下,如果我们有一个指针在抛异常之前,而指针的释放在抛异常之后的话,当出现异常后,编译器会直接跳到捕获异常处,从而这个指针就不会被释放,从而造成内存泄漏的问题。

    什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内
    存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对
    该段内存的控制,因而造成了内存的浪费。
    内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现
    内存泄漏会导致响应越来越慢,最终卡死。

    2.智能指针的原理 

    2.1 RAII 

    RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内
    存、文件句柄、网络连接、互斥量等等)的简单技术。
    在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在
    对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做
    法有两大好处:
    • 不需要显式地释放资源。
    • 采用这种方式,对象所需的资源在其生命期内始终保持有效. 

    3.C++11和boost中智能指针的关系 

    (1) C++ 98 中产生了第一个智能指针auto_ptr.
    (2) C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr.
    (3) C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。
    (4) C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。

     

    4.auto_ptr 

     C++98版本的库中就提供了auto_ptr的智能指针。auto_ptr的实现原理:管理权转移的思想。

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. using namespace std;
    7. namespace L
    8. {
    9. template <class T>
    10. class AutoPtr
    11. {
    12. public:
    13. AutoPtr(T* ptr)
    14. :_ptr(ptr)
    15. {}
    16. ~AutoPtr()
    17. {
    18. delete _ptr;
    19. }
    20. T& operator*()
    21. {
    22. return *_ptr;
    23. }
    24. T* operator->()
    25. {
    26. return _ptr;
    27. }
    28. private:
    29. T* _ptr;
    30. };

     auto_ptr通俗来说是浅拷贝,会导致两个指针同时指向同一资源,导致被拷贝对象悬空,因此很多公司明确要求不能使用它。

    5.unique_ptr 

    unique_ptr的实现原理:简单粗暴的防拷贝。
    1. template <class T>
    2. class UniquePtr
    3. {
    4. public:
    5. UniquePtr(T* ptr)
    6. :_ptr(ptr)
    7. {}
    8. ~UniquePtr()
    9. {
    10. delete _ptr;
    11. }
    12. T& operator*()
    13. {
    14. return *_ptr;
    15. }
    16. T* operator->()
    17. {
    18. return _ptr;
    19. }
    20. //c++11
    21. UniquePtr(const UniquePtr<T>& ptr) = delete;
    22. //c++98
    23. /*private:
    24. UniquePtr(const UniquePtr<T>& ptr);*/
    25. private:
    26. T* _ptr;
    27. };

    直接不让资源进行拷贝,把拷贝构造给私有或者delete。

    6.shared_ptr 

    shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源 

    • shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共
    • 对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
    • 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源。
    • 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

     

    1. // 引用计数支持多个拷贝管理同一个资源,最后一个析构对象释放资源
    2. //循环引用 -> 增加一个类weakptr,不增加引用计数即可
    3. template <class T>
    4. class SharedPtr
    5. {
    6. public:
    7. SharedPtr(T* ptr = nullptr)
    8. :_ptr(ptr)
    9. ,_pcount(new int(1))
    10. ,_mtx(new mutex)
    11. {}
    12. template <class D>
    13. SharedPtr(T* ptr,D del)
    14. :_ptr(ptr)
    15. , _pcount(new int(1))
    16. , _mtx(new mutex)
    17. ,_del(del)
    18. {}
    19. void release()
    20. {
    21. bool delete_flag = false;
    22. _mtx->lock();
    23. if (--(*_pcount) == 0)
    24. {
    25. if (_ptr)
    26. {
    27. _del(_ptr);
    28. }
    29. delete _pcount;
    30. delete_flag = true;
    31. }
    32. _mtx->unlock();
    33. if (delete_flag)
    34. {
    35. delete _mtx;
    36. }
    37. }
    38. ~SharedPtr()
    39. {
    40. release();
    41. }
    42. T& operator*()
    43. {
    44. return *_ptr;
    45. }
    46. T* operator->()
    47. {
    48. return _ptr;
    49. }
    50. T* get()
    51. {
    52. return _ptr;
    53. }
    54. SharedPtr(const SharedPtr<T>& sptr)
    55. {
    56. _ptr = sptr._ptr;
    57. _pcount = sptr._pcount;
    58. _mtx = sptr._mtx;
    59. add_count();
    60. }
    61. int use_count()
    62. {
    63. return *_pcount;
    64. }
    65. void add_count()
    66. {
    67. _mtx->lock();
    68. ++_pcount;
    69. _mtx->unlock();
    70. }
    71. SharedPtr<T>& operator=(const SharedPtr<T>& sptr)
    72. {
    73. if (_ptr != sptr._ptr)
    74. {
    75. if (--(*_pcount) == 0)
    76. {
    77. release();
    78. }
    79. _ptr = sptr._ptr;
    80. _pcount = sptr._pcount;
    81. _mtx = sptr._mtx;
    82. add_count();
    83. }
    84. return *this;
    85. }
    86. private:
    87. T* _ptr;
    88. int* _pcount;
    89. mutex* _mtx;
    90. function<void(T*)> _del = [](T* ptr) {delete ptr; };
    91. };

    6.1 线程安全问题

    需要注意的是shared_ptr的线程安全分为两方面:
    • 智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或--,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2.这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++、--是需要加锁的,也就是说引用计数的操作是线程安全的。
    • 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。

     因此在析构以及增加指针的引用计数时需要增加互斥锁来保证线程安全。

    6.2 循环引用问题 

    当我们在给一个双向循环链表结构用shared_ptr时,可能会出现循环引用问题。 

    • node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动delete。
    • node1的_next指向node2,node2的_prev指向node1,引用计数变成2。
    • node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点。
    • 也就是说_next析构了,node2就释放了。
    • 也就是说_prev析构了,node1就释放了。
    • 但是_next属于node1的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放。

    // 解决方案:在引用计数的场景下,把节点中的_prev_next改成weak_ptr就可以了  

    // 原理就是, node1->_next = node2; node2->_prev = node1 时, weak_ptr _next
    _prev 不会增加 node1 node2 的引用计数。

     

    1. template <class T>
    2. class WeakPtr
    3. {
    4. public:
    5. WeakPtr()
    6. :_ptr(nullptr)
    7. {}
    8. WeakPtr(const SharedPtr<T>& sptr)
    9. :_ptr(sptr.get())
    10. {}
    11. T& operator*()
    12. {
    13. return *_ptr;
    14. }
    15. T* operator->()
    16. {
    17. return _ptr;
    18. }
    19. T* get()
    20. {
    21. return _ptr;
    22. }
    23. private:
    24. T* _ptr;
    25. };
    26. }

     6.3 删除器(了解)

    不是new出来的对象的话可以通过删除器来解决问题。

    1. // 仿函数的删除器
    2. template<class T>
    3. struct FreeFunc {
    4. void operator()(T* ptr)
    5. {
    6. cout << "free:" << ptr << endl;
    7. free(ptr);
    8. }
    9. };
    10. template<class T>
    11. struct DeleteArrayFunc {
    12. void operator()(T* ptr)
    13. {
    14. cout << "delete[]" << ptr << endl;
    15. delete[] ptr;
    16. }
    17. };

     

  • 相关阅读:
    ubuntu linux 环境下的程序打包
    PEG/蛋白/抗体功能/修饰/偶联化铋纳米球 PEG/Protein/antibody-Bi nanosphere 的产品介绍
    2023-10-10 mysql-{mysql_alter_table}-出错后回滚-记录
    只需4步使用Redis缓存优化Node.js应用
    Java版分布式微服务云开发架构 Spring Cloud+Spring Boot+Mybatis 电子招标采购系统功能清单
    【HMS core】【Push Kit】【FAQ】典型问题合集5
    CSS:弹性布局(display:flex)
    Sentinel控制台1.8.3修改源码,修改配置后推送到Nacos
    .NET8.0 AOT 经验分享 - 专项测试各大 ORM 是否支持
    Cilium 1.11:服务网格的未来已来
  • 原文地址:https://blog.csdn.net/m0_72445027/article/details/134382363