理解C++智能指针

た 入场券 2022-06-09 00:10 307阅读 0赞

C++智能指针是面试中经常会问到的一个经典知识点,本身使用也具有很大的意义。本文从下面三个方面对智能指针的内容进行整理,以期对智能指针能够有一个较为清晰的认识:
1 智能指针的实现原理
2 常用的智能指针
3 智能指针的实现


1、智能指针的实现原理:
智能指针是一个类,且这个类是个模板类,为了适应不同基本类型的需求,它在构造函数中传入一个普通指针,将这个基本类型指针封装为类对象指针,并在析构函数中释放这个指针,删除该指针指向的内存空间。因为一般使用时,智能指针的类都是局部对象,所以当函数(或程序)自动结束时会自动被释放。

2、 常用的智能指针:
STL一共提供了四种智能指针:auto_ptr,unique_ptr,shared_ptr和weak_ptr

对于所有的智能指针需要注意以下几点
1) 所有的智能指针类都有一个explicit构造函数(阻止不应该允许的经过转换构造函数进行的隐式转换的发生,即不能在隐式转换中使用),因此不能直接将指针转换为只能指针对象,必须显式调用,即

  1. int* test=new int(2);
  2. std::shared_ptr<int> p1=test; //编译报错,无法从“int *”转换
  3. //为“std::tr1::shared_ptr<_Ty>”
  4. //因为构造函数被声明为explicit,必须显式调用
  5. std::shared_ptr<int> p2(test); //正确,显式调用

2) 对全部三种智能指针都应避免的一点:

  1. string vacation("hello,world");
  2. shared_ptr<string> pvac(&vacation); // 错误,No pvac过期时,程序将把delete运算符用于非堆内存,导致错误
  3. shared_ptr<string> pvac1(new string("hello,world")); //正确

下面简要说明四种智能指针的特点:

1)std::auto_ptr:属于独占内存的方式,当p1=p2;时,p2的内存使用权转移给p1(p1指向p2之前所指向的地址),p2成为空悬指针(指针地址为0),若之后使用p2,可以编译通过,但运行时会出现内存访问错误,不安全,会出现内存崩溃的问题。也因此不能被放入容器中(C++11已将其摒弃)

  1. int* test=new int(2);
  2. std::auto_ptr<int> p1(new int(5));
  3. printf("p1:%d\n",p1);
  4. std::auto_ptr<int> p2(test);
  5. std::auto_ptr<int> p3=p1; //不报编译错误
  6. printf("p3:%d\n",p3);
  7. printf("p1:%d\n",p1);
  8. //std::auto_ptr<int> p4(p2); //运行时报访问错误
  9. //printf("%d\n",*p1); //报访问错误,因为p1将内存管理权转移给p3了,p1悬空
  10. printf("%d\n",*p2);

这里写图片描述

2)C++引入的unique_ptr,也属于独享内存所有权,但优于auto_ptr,拷贝构造函数和赋值函数只有声明没有定义,且为私有函数,不能使用。直接赋值会编译出错。需要赋值的时候用std::move

  1. std::unique_ptr<int> p5(new int(8));
  2. std::unique_ptr<int> p6(test);
  3. printf("%d\n",*p5);
  4. printf("%d\n",*p6);
  5. //std::unique_ptr<int> p7(p5); //编译报错,库内为private成员,且只声明,未定义
  6. //std::unique_ptr<int> p8=p6; //编译报错,库内为private成员,且只声明,未定义
  7. //printf("%d\n",*p7);
  8. //printf("%d\n",*p8);
  9. std::unique_ptr<int> p9=unique_ptr<int>(test);
  10. printf("%d\n",*p9);
  11. printf("p9:%d\n",p9);
  12. p6=std::move(p9);
  13. printf("p6:%d\n",p6);
  14. printf("p9:%d\n",p9);
  15. //printf("%d\n",*p6); //内存访问出错,因为p6转移了内存所有权

这里写图片描述
3)shared_ptr(boost、C++11)属于共享内存,内部有引用计数机制(实现方式有两种,一种是辅助类,一种是句柄类),对一个内存对象进行引用计数,当删除其中一个指向该内存的指针时,引用计数减1,但并不会释放该内存对象,只有当该内存对象的引用计数减为0时,才会释放该块内存,避免了指针空悬、内存访问错误的情况。

  1. std::shared_ptr<int> p10(new int(10));
  2. std::shared_ptr<int> p11(test);
  3. printf("%d\n",*p10);
  4. printf("%d\n",*p11);
  5. printf("p11->use_count:%d\n",p11.use_count());
  6. std::shared_ptr<int> p12(p10); //编译正常
  7. std::shared_ptr<int> p13=p11; //编译正常
  8. //std::weak_ptr<int> p13=p11; //编译正常
  9. printf("p11->use_count:%d\n",p11.use_count());
  10. printf("p13->use_count:%d\n",p13.use_count());
  11. printf("p11:%d\n",p11);
  12. printf("p13:%d\n",p13);
  13. //p11=p13; //编译正常
  14. printf("p11->use_count:%d\n",p11.use_count());
  15. printf("p13->use_count:%d\n",p13.use_count());
  16. printf("p11:%d\n",p11);
  17. printf("p13:%d\n",p13);
  18. //std::shared_ptr<int> p13=test; //编译报错,无法从“int *”转换为“std::tr1::shared_ptr<_Ty>”,因为构造函数被声明为explicit,必须显式调用
  19. printf("%d\n",*p11);
  20. printf("%d\n",*p12);
  21. //printf("%d\n",*p13);
  22. std::shared_ptr<int> p14=std::move(p10);
  23. printf("%d\n",*p14);
  24. p10 = p14;
  25. printf("%d\n",*p10); //内存访问出错,因为p10转移了内存所有权
  26. string vacation("I wandered lonely as a cloud.");
  27. shared_ptr<string> pvac(&vacation); // No
  28. cout<<*pvac;

这里写图片描述
4)weak_ptr(boost、C++11)为shared_ptr设计,弱引用。只对shared_ptr进行引用,但不改变其计数;所以,当被引用的shared_ptr失效后,相应的weak_ptr也会失效。测试代码如下:

  1. std::shared_ptr<int> p10(new int(10));
  2. std::shared_ptr<int> p11(test);
  3. printf("%d\n",*p10);
  4. printf("%d\n",*p11);
  5. printf("p11->use_count:%d\n",p11.use_count());
  6. std::shared_ptr<int> p12(p10); //编译正常
  7. //std::shared_ptr<int> p13=p11; //编译正常
  8. std::weak_ptr<int> p13=p11; //编译正常
  9. printf("p11->use_count:%d\n",p11.use_count());
  10. printf("p13->use_count:%d\n",p13.use_count());
  11. printf("p11:%d\n",p11);
  12. printf("p13:%d\n",p13);
  13. //p11=p13; //编译正常
  14. printf("p11->use_count:%d\n",p11.use_count());
  15. printf("p13->use_count:%d\n",p13.use_count());
  16. printf("p11:%d\n",p11);
  17. printf("p13:%d\n",p13);
  18. //std::shared_ptr<int> p13=test; //编译报错,无法从“int *”转换为“std::tr1::shared_ptr<_Ty>”,因为构造函数被声明为explicit,必须显式调用
  19. printf("%d\n",*p11);
  20. printf("%d\n",*p12);
  21. //printf("%d\n",*p13);
  22. std::shared_ptr<int> p14=std::move(p10);
  23. printf("%d\n",*p14);
  24. p10 = p14;
  25. printf("%d\n",*p10); //内存访问出错,因为p10转移了内存所有权
  26. string vacation("I wandered lonely as a cloud.");
  27. shared_ptr<string> pvac(&vacation); // No
  28. cout<<*pvac;

这里写图片描述
看起来weak_ptr没有什么实质的作用,但实际上weak_ptr 主要用在软件架构设计中,可以在基类(此处的基类并非抽象基类,而是指继承于抽象基类的虚基类)中定义一个 boost::weak_ptr,用于指向子类的boost::shared_ptr,这样基类仅仅观察自己的 boost::weak_ptr 是否为空就知道子类有没对自己赋值了,而不用影响子类 boost::shared_ptr 的引用计数,用以降低复杂度,更好的管理对象

3、 智能指针的实现
这里采用上文提到的辅助类的方式,具体代码如下:

  1. //辅助类,用来引用计数
  2. template<class friendclass,class T>
  3. class U_ptr
  4. {
  5. friend friendclass;
  6. T *_p;
  7. int use_count;
  8. U_ptr(T *p):_p(p),use_count(1)
  9. {
  10. cout<<"U_ptr constructor called!"<<endl;
  11. }
  12. ~U_ptr()
  13. {
  14. if(use_count==0)
  15. delete _p;
  16. cout<<"U_ptr destructor has called!"<<endl;
  17. }
  18. };
  19. template<class T>
  20. class smartpointer
  21. {
  22. private:
  23. U_ptr<smartpointer,T>* _ptr;
  24. public:
  25. smartpointer(T* ptr):_ptr(new U_ptr<smartpointer,T>(ptr)) //构造函数
  26. {
  27. cout<<"smartpointer constructor called!"<<endl;
  28. }
  29. T& operator *() //重载*运算符
  30. {
  31. return *(_ptr->_p);
  32. }
  33. smartpointer& operator=(const smartpointer &sptr) //重载=运算符
  34. {
  35. ++(sptr._ptr->use_count); //在减少左操作数的使用计数之前使rhs的使用计数加1,是为了防止自身赋值
  36. if(--_ptr->use_count==0)
  37. delete _ptr;
  38. _ptr=sptr._ptr;
  39. return *this;
  40. }
  41. smartpointer(const smartpointer &sptr):_ptr(sptr._ptr) //拷贝构造函数
  42. {
  43. ++_ptr->use_count;
  44. cout<<"smartpointer copy constructor called!"<<endl;
  45. }
  46. int use_count() //获取引用计数值
  47. {
  48. return _ptr->use_count;
  49. }
  50. T* get_ptr() //获取指针地址
  51. {
  52. return _ptr->_p;
  53. }
  54. ~smartpointer() //析构函数,计数为0时才释放指针指向的内存
  55. {
  56. cout<<_ptr->_p<<" smartpointer deconstructor called!"<<endl;
  57. cout<<"before destruction use_count is="<<use_count()<<endl;
  58. if(--_ptr->use_count==0)
  59. delete _ptr;
  60. }
  61. };
  62. int test()
  63. {
  64. smartpointer<int> p1(new int(8));//a should only be use to construct once
  65. cout<<"p1.use_count="<<p1.use_count()<<endl;
  66. cout<<"p1="<<p1.get_ptr()<<endl;
  67. smartpointer<int> p2(p1);
  68. cout<<"p1.use_count="<<p1.use_count()<<endl;
  69. cout<<"p2.use_count="<<p2.use_count()<<endl;
  70. cout<<"p1="<<p1.get_ptr()<<endl;
  71. cout<<"p2="<<p2.get_ptr()<<endl;
  72. smartpointer<int> p3(p1);
  73. cout<<"p1.use_count="<<p1.use_count()<<endl;
  74. cout<<"p3.use_count="<<p3.use_count()<<endl;
  75. cout<<"p1="<<p1.get_ptr()<<endl;
  76. cout<<"p3="<<p3.get_ptr()<<endl;
  77. smartpointer<int> p4(p3);
  78. cout<<"p3.use_count="<<p3.use_count()<<endl;
  79. cout<<"p4.use_count="<<p4.use_count()<<endl;
  80. cout<<"p3="<<p3.get_ptr()<<endl;
  81. cout<<"p4="<<p4.get_ptr()<<endl;
  82. p4 = p4;
  83. cout<<"p4.use_count="<<p4.use_count()<<endl;
  84. p4 = p3;
  85. cout<<"p4.use_count="<<p4.use_count()<<endl;
  86. cout<<"p3.use_count="<<p3.use_count()<<endl;
  87. cout<<"p4="<<p4.get_ptr()<<endl;
  88. cout<<"p3="<<p3.get_ptr()<<endl;
  89. return 0;
  90. }
  91. int main(){
  92. test();
  93. return 0;
  94. }

执行结果如下:

这里写图片描述

与实际应用STL中的智能指针结果相近

发表评论

表情:
评论列表 (有 0 条评论,307人围观)

还没有评论,来说两句吧...

相关阅读

    相关 C++ 智能指针

    C++ 智能指针 为了更安全地使用动态对象,标准库定义了两个只能指针类型来管理动态分配的对象当一个对象应该被释放时,指向它的智能指针可以确保自动地把它释放。 理论上来

    相关 理解C++智能指针

    C++智能指针是面试中经常会问到的一个经典知识点,本身使用也具有很大的意义。本文从下面三个方面对智能指针的内容进行整理,以期对智能指针能够有一个较为清晰的认识: 1 智能指

    相关 C++智能指针

    1.智能指针的作用        C++程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理。程序员自己管理堆内存可以提高了程序的效率,但是整体来说