• C++:详细的说明智能指针的使用以及底层实现,以及删除器和包装器的使用


    1.智能指针

    1.1概念:

    智能指针并非指针,他是一个类对象。这个对象托管了堆区的资源。

     1.2机制:

    当栈上的智能指针对象出栈时,所托管的堆区对象自动进行析构。

    用到的知识点就是栈上对象出栈自动销毁的特性,因为出栈即自动调用析构。

    RAII 是 resource acquisition is initialization 的缩写,意为“资源获取即初始化”。它是 C++ 之父 Bjarne Stroustrup 提出的设计理念,其核心是把资源和对象的生命周期绑定,对象创建获取资源,对象销毁释放资源。在 RAII 的指导下,C++ 把底层的资源管理问题提升到了对象生命周期管理的更高层次。

    那么到底什么是 RALL 机制?

    使用 C++ 时,最让人头疼的便是内存管理,但却又正是对内存高度的可操作性给了 C++ 程序猿极大的自由与装逼资本。

    当我们 new 出一块内存空间,在使用完之后,如果不使用 delete 来释放这块资源则将导致内存泄露,这在中大型项目中是极具破坏性的。但是人无完人,我们并不能保证每次都记得释放无法再次获取到且不再使用的内存。

    2.auto_ptr资源所有权转移智能指针

    2.1auto_ptr的使用:

    1. #include
    2. #include
    3. using namespace std;
    4. class Stu
    5. {
    6. int age;
    7. string name;
    8. public:
    9. Stu(string name,int age)
    10. {
    11. this->age=age;
    12. this->name=name;
    13. cout<<"STU的构造"<
    14. }
    15. ~Stu()
    16. {
    17. cout<<"STU的析构"<
    18. }
    19. void show_info()
    20. {
    21. cout<<this->age<<this->name<
    22. }
    23. };
    24. int main()
    25. {
    26. auto_ptr stu(new Stu("lisi",12));
    27. stu->show_info();
    28. return 0;
    29. }

    结果图:

     分析:像这样我们即使不手动释放堆区的资源,他也会自动释放。

    2.2auto_ptr的底层实现:

    1. #include
    2. using namespace std;
    3. class Stu
    4. {
    5. string name;
    6. int age;
    7. public:
    8. Stu(string name,int age)
    9. {
    10. this->name=name;
    11. this->age=age;
    12. cout<<"stu的构造"<
    13. }
    14. ~Stu()
    15. {
    16. cout<<"stu的析构"<
    17. }
    18. void show_info()
    19. {
    20. cout<<this->name<<","<<this->age<
    21. }
    22. };
    23. template <class T>
    24. class Auto_ptr
    25. {
    26. T* ptr;
    27. public:
    28. Auto_ptr(T* _ptr)
    29. {
    30. this->ptr=_ptr;
    31. }
    32. ~Auto_ptr()
    33. {
    34. delete ptr;
    35. ptr=NULL;
    36. }
    37. Auto_ptr(const Auto_ptr& other)
    38. {
    39. this->ptr=other.ptr;
    40. const_cast(other)->ptr=nullptr;
    41. }
    42. Auto_ptr& operator=(const Auto_ptr& other)
    43. {
    44. if(this->ptr!=nullptr){
    45. delete this->ptr;
    46. this->ptr=nullptr;
    47. }
    48. this->ptr=other.ptr;
    49. }
    50. T* get()
    51. {
    52. return this->ptr;
    53. }
    54. T* operator->()
    55. {
    56. return this->ptr;
    57. }
    58. };
    59. int main()
    60. {
    61. Auto_ptr stu(new Stu("lisi",20));
    62. stu.operator->()->show_info();
    63. stu->show_info();
    64. return 0;
    65. }

    结果图:

     分析:这就是智能指针auto_ptr的实现。

    2.3auto_ptr的缺点:

    2.3.1通过代码来说明问题:

    1. #include
    2. #include
    3. using namespace std;
    4. class Stu
    5. {
    6. string name;
    7. int age;
    8. public:
    9. Stu(string name,int age)
    10. {
    11. this->name=name;
    12. this->age=age;
    13. cout<<"stu的构造"<
    14. }
    15. ~Stu()
    16. {
    17. cout<<"stu的析构"<
    18. }
    19. void show_info()
    20. {
    21. cout<<this->name<<","<<this->age<
    22. }
    23. };
    24. template <class T>
    25. class Auto_ptr
    26. {
    27. T* ptr;
    28. public:
    29. Auto_ptr(T* _ptr)
    30. {
    31. this->ptr=_ptr;
    32. cout<<"构造"<
    33. }
    34. ~Auto_ptr()
    35. {
    36. delete ptr;
    37. ptr=NULL;
    38. cout<<"析构"<
    39. }
    40. Auto_ptr(const Auto_ptr& other)
    41. {
    42. this->ptr=other.ptr;
    43. const_cast(other)->ptr=nullptr;
    44. }
    45. Auto_ptr& operator=(const Auto_ptr& other)
    46. {
    47. if(this==&other){
    48. return *this;
    49. }
    50. if(this->ptr!=nullptr){
    51. delete this->ptr;
    52. this->ptr=nullptr;
    53. }
    54. this->ptr=other.ptr;
    55. }
    56. T* get()
    57. {
    58. return this->ptr;
    59. }
    60. T* operator->()
    61. {
    62. return this->ptr;
    63. }
    64. };
    65. int main()
    66. {
    67. Auto_ptr stu(new Stu("zhangsan",20));
    68. stu->show_info();
    69. Auto_ptr stu1=stu;
    70. stu1->show_info();
    71. return 0;
    72. }

    报错图:

    分析:当我们在main函数里面这么写的时候就会出现问题,就是我只要以把数值赋给另外一个,原来的那个就会消失,要是再大型项目中,这肯定会导致很多小问题,而且我们从这个报错也看到了,系统并不让我们删除,也说明了这个问题的严重性,所以这个智能指针被废弃了,当然也还能用哈。

    3.带有引用记数器的共享智能指针shared_ptr(重点

    3.1shared_ptr的使用方法:

    1. #include
    2. #include
    3. using namespace std;
    4. class Stu
    5. {
    6. string name;
    7. int age;
    8. public:
    9. Stu(string name,int age)
    10. {
    11. this->name=name;
    12. this->age=age;
    13. cout<<"stu的构造"<
    14. }
    15. ~Stu()
    16. {
    17. cout<<"stu的析构"<
    18. }
    19. void show_info()
    20. {
    21. cout<<this->name<<","<<this->age<
    22. }
    23. };
    24. int main()
    25. {
    26. shared_ptr stu(new Stu("lisi",23));
    27. stu->show_info();
    28. cout<use_count()<
    29. shared_ptr stu1=stu;
    30. stu1->show_info();
    31. cout<use_count()<
    32. return 0;
    33. }

    结果图:

     3.2shared_ptr的底层实现:

    1. #include
    2. using namespace std;
    3. using namespace std;
    4. class Stu
    5. {
    6. string name;
    7. int age;
    8. public:
    9. Stu(string name,int age)
    10. {
    11. this->name=name;
    12. this->age=age;
    13. cout<<"stu的构造"<
    14. }
    15. ~Stu()
    16. {
    17. cout<<"stu的析构"<
    18. }
    19. void show_info()
    20. {
    21. cout<<this->name<<","<<this->age<
    22. }
    23. };
    24. template <class T>
    25. class Recounts
    26. {
    27. T* ptr;
    28. int counts;
    29. public:
    30. Recounts(T* _ptr=nullptr)
    31. {
    32. this->ptr=_ptr;
    33. if(this->ptr!=nullptr){
    34. this->counts=1;
    35. }
    36. }
    37. void my_add()
    38. {
    39. ++(this->counts);
    40. }
    41. int my_sub()
    42. {
    43. return --(this->counts);
    44. }
    45. int get_counts()
    46. {
    47. return this->counts;
    48. }
    49. };
    50. template <class T>
    51. class Shared_ptr
    52. {
    53. Recounts* recounts;
    54. T* ptr;
    55. public:
    56. Shared_ptr(T* _ptr=nullptr)
    57. {
    58. this->ptr=_ptr;
    59. if(this->ptr!=nullptr){
    60. recounts=new Recounts(ptr);
    61. }
    62. }
    63. ~Shared_ptr()
    64. {
    65. if(recounts->my_sub()==0){
    66. delete this->ptr;
    67. delete recounts;
    68. this->ptr=nullptr;
    69. recounts=nullptr;
    70. }
    71. }
    72. Shared_ptr(const Shared_ptr& other)
    73. {
    74. this->ptr=other.ptr;
    75. this->recounts=other.recounts;
    76. recounts->my_add();
    77. }
    78. Shared_ptr& operator=(const Shared_ptr& other)
    79. {
    80. if(this==&other){
    81. return *this;
    82. }
    83. if(this->ptr!=nullptr){
    84. delete this->ptr;
    85. this->ptr=nullptr;
    86. }
    87. this->ptr=other.ptr;
    88. this->recounts=other.recounts;
    89. recounts->my_add();
    90. }
    91. T* operator->()
    92. {
    93. return this->ptr;
    94. }
    95. int usr_counts()
    96. {
    97. return recounts->get_counts();
    98. }
    99. };
    100. int main()
    101. {
    102. Shared_ptr stu(new Stu("lisi",20));
    103. stu->show_info();
    104. stu.usr_counts();
    105. Shared_ptr stu1=stu;
    106. stu1->show_info();
    107. stu1.usr_counts();
    108. return 0;
    109. }

    结果图:

    4.weak_ptr

    4.1概念:

    和shared_ptr搭配着用,防止内存泄漏。

    4.1代码说明:

    4.2.1shared_ptr的缺点:

    1. #include
    2. #include
    3. using namespace std;
    4. class B;
    5. class A
    6. {
    7. public:
    8. shared_ptr ptr_b;
    9. A()
    10. {
    11. cout<<"A的构造"<
    12. }
    13. ~A()
    14. {
    15. cout<<"A的析构"<
    16. }
    17. void show_info()
    18. {
    19. cout<<"mrr ai chi shi"<
    20. }
    21. };
    22. class B
    23. {
    24. public:
    25. shared_ptr ptr_a;

    结果图:

     由结果图片我们可以看出来,没有进行析构,这是由于当给pa给与堆区地址的时候,计数器的数字加1,当又进行拷贝赋值的时候,pa->ptr_b又在pa的基础上又加了1,所以pa->ptr->b的计数器的数字为2,又因为他们都共享一块内存pb的计数器数字也为2。

    具体实现图:

     这个时候我们就可以引出来我们的weak_ptr和shared_ptr是天然的搭档。

    4.2weak_ptr和shared_ptr的天然搭配代码:

    1. #include
    2. #include
    3. using namespace std;
    4. class B;
    5. class A
    6. {
    7. public:
    8. weak_ptr ptr_b;
    9. A()
    10. {
    11. cout<<"A的构造"<
    12. }
    13. ~A()
    14. {
    15. cout<<"A的析构"<
    16. }
    17. void show_info()
    18. {
    19. cout<<"mrr ai chi shi"<
    20. }
    21. };
    22. class B
    23. {
    24. public:
    25. weak_ptr ptr_a;

    结果图:

     分析:因为使用weak_ptr后计数器不会加1, 这样就能防止内存泄漏了。

    5.独占智能指针:unique_ptr

    5.1概念:

    独一无二的,唯一的智能指针,他是不共享的,他不想其它的共享对象也操作他的指向的堆区空间,他是唯一一个可操作这个堆区空间的对象。也就是说他的类对象是不可以发生拷贝构造与赋值运算符重载的。

    5.2使用方式:

    1. #include
    2. #include
    3. using namespace std;
    4. class A
    5. {
    6. public:
    7. A()
    8. {
    9. cout<<"A的构造"<
    10. }
    11. ~A()
    12. {
    13. cout<<"A的析构"<
    14. }
    15. void show_info()
    16. {
    17. cout<<"mrr ai chi shi"<
    18. }
    19. };
    20. int main()
    21. {
    22. unique_ptr a(new A);

    结果图:
     

     这个智能指针修饰的内存只能一个人享用,如图:

     5.3底层实现:

    1. #include
    2. #include
    3. using namespace std;
    4. class A
    5. {
    6. public:
    7. A()
    8. {
    9. cout<<"A的构造"<
    10. }
    11. ~A()
    12. {
    13. cout<<"A的析构"<
    14. }
    15. void show_info()
    16. {
    17. cout<<"mrr ai chi shi"<
    18. }
    19. };
    20. template <class T>
    21. class Unique_ptr
    22. {
    23. T* ptr;
    24. public:
    25. Unique_ptr(T* _ptr)
    26. {
    27. this->ptr=_ptr;
    28. }
    29. Unique_ptr(const Unique_ptr& other)=delete ;
    30. Unique_ptr& operator=(const Unique_ptr& other)=delete;
    31. ~Unique_ptr()
    32. {
    33. delete this->ptr;
    34. this->ptr=nullptr;
    35. }
    36. T* operator->()
    37. {
    38. return this->ptr;
    39. }
    40. };
    41. int main()
    42. {
    43. Unique_ptr a(new A);

    6.智能指针与自定义删除器及包装器的使用:

    6.1为什么要定义删除器:

    我们可以看一下上面几根智能指针的实现,都是delete ptr;并不是delete []ptr,如果我们不自定义删除的话,就会导致内存泄漏,如下:

    1. #include
    2. #include
    3. using namespace std;
    4. class A
    5. {
    6. public:
    7. A()
    8. {
    9. cout<<"A的构造"<
    10. }
    11. ~A()
    12. {
    13. cout<<"A的析构"<
    14. }
    15. };
    16. int main()
    17. {
    18. shared_ptr a(new A[5]);

    结果图:


     

     分析:由图可知,构造了五次,析构了一次,造成了内存泄漏,这就是为什么我们要进行用删除器。

    6.2自定义的删除器(函数对象):

    1. #include
    2. #include
    3. using namespace std;
    4. template <class T>
    5. class Deleter
    6. {
    7. public:
    8. void operator()(T* p)
    9. {
    10. delete [] p;
    11. }
    12. };
    13. class A
    14. {
    15. public:
    16. A()
    17. {
    18. cout<<"A的构造"<
    19. }
    20. ~A()
    21. {
    22. cout<<"A的析构"<
    23. }
    24. };
    25. int main()
    26. {

    结果图:

     

    6.3自定义的删除器(匿名6.4函数对象):

    1. #include
    2. #include
    3. using namespace std;
    4. template <class T>
    5. class Deleter
    6. {
    7. public:
    8. void operator()(T* p)
    9. {
    10. delete [] p;
    11. }
    12. };
    13. class A
    14. {
    15. public:
    16. A()
    17. {
    18. cout<<"A的构造"<
    19. }
    20. ~A()
    21. {
    22. cout<<"A的析构"<
    23. }
    24. };
    25. int main()
    26. {

    结果图:

     6.4智能指针的C++官方提供的释放连续空间的删除器

    注意:每个智能指针删除所放的位置不一定相同,可以自己去查一下c++官方手册。

    1. #include
    2. #include
    3. using namespace std;
    4. template <class T>
    5. class Deleter
    6. {
    7. public:
    8. void operator()(T* p)
    9. {
    10. delete [] p;
    11. }
    12. };
    13. class A
    14. {
    15. public:
    16. A()
    17. {
    18. cout<<"A的构造"<
    19. }
    20. ~A()
    21. {
    22. cout<<"A的析构"<
    23. }
    24. };
    25. int main()
    26. {
    27. unique_ptr> a(new A[5]);
    28. //unique_ptr> a(new A[5]);//这个也是可以的
    29. return 0;
    30. }

    结果图:
     

     6.5包装一个匿名函数对象的类型:

    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. template <class T>
    6. class Deleter
    7. {
    8. public:
    9. void operator()(T* p)
    10. {
    11. delete [] p;
    12. }
    13. };
    14. class A
    15. {
    16. public:
    17. A()
    18. {
    19. cout<<"A的构造"<
    20. }
    21. ~A()
    22. {
    23. cout<<"A的析构"<
    24. }
    25. };
    26. int main()
    27. {
    28. unique_ptrvoid (A*)>> a(new A[5],Deleter());

    6.5.1包装器的使用:可以提升函数的效率,就想变成()函数一样,效率很高。

    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. template <class T>
    6. class Deleter
    7. {
    8. public:
    9. void operator()(T* p)
    10. {
    11. delete [] p;
    12. }
    13. };
    14. class A
    15. {
    16. public:
    17. A()
    18. {
    19. cout<<"A的构造"<
    20. }
    21. ~A()
    22. {
    23. cout<<"A的析构"<
    24. }
    25. };
    26. void show_info()
    27. {
    28. cout<<"加油"<
    29. }
    30. int main()
    31. {
    32. function<void ()> f=show_info;
    33. f();
    34. return 0;
    35. }

  • 相关阅读:
    vue @cliick.stop @click.prevent @click.self
    一文带你深入了解JVM性能调优以及对JVM调优的全面总结
    【探索Linux】—— 强大的命令行工具 P.8(进程优先级、环境变量)
    UDS诊断入门
    python3GUI--详细讲解一个QQ音乐组件的制作By:PyQt5(详细介绍、附源代码)
    ROS Turtlebot3多机器人编队导航仿真
    带你走进Cflow (一)
    Node-v14.20.0 Windows下的环境变量配置
    在2024年WWDC大会上,苹果宣布了其全新的“Apple Intelligence”AI功能以及ISO18功能
    如何正确的清理C盘
  • 原文地址:https://blog.csdn.net/a2998658795/article/details/126069592