当前位置:   article > 正文

【C++11】智能指针详解

智能指针

「前言」文章是关于C++11的智能指针方面 

「归属专栏」C嘎嘎

「主页链接」个人主页

「笔者」枫叶先生(fy)

一、 为何需要智能指针

1.1 内存泄漏问题

关于内存泄漏的问题,例如下面的代码:

  1. #include <iostream>
  2. using namespace std;
  3. int div()
  4. {
  5. int a, b;
  6. cin >> a >> b;
  7. if (b == 0)
  8. throw invalid_argument("除0错误");
  9. return a / b;
  10. }
  11. void Func()
  12. {
  13. int* p1 = new int;
  14. int* p2 = new int;
  15. cout << div() << endl;
  16. delete p1;
  17. delete p2;
  18. }
  19. int main()
  20. {
  21. try
  22. {
  23. Func();
  24. }
  25. catch (exception& e)
  26. {
  27. cout << e.what() << endl;
  28. }
  29. return 0;
  30. }

执行上述代码时,如果输入的除数为0,那么div函数中就会抛出异常,这时程序的执行流会直接跳转到主函数中的 catch块中执行,最终导致 Func函数中申请的内存资源没有得到释放

对于这种情况,我们可以在 Func函数中先对 div函数中抛出的异常进行捕获,捕获后先将之前申请的内存资源释放,然后再将异常重新抛出,代码如下

  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. int* p1 = new int;
  12. int* p2 = new int;
  13. try
  14. {
  15. cout << div() << endl;
  16. }
  17. catch (...)
  18. {
  19. delete p1;
  20. delete p2;
  21. throw;
  22. }
  23. delete p1;
  24. delete p2;
  25. }
  26. int main()
  27. {
  28. try
  29. {
  30. Func();
  31. }
  32. catch (exception& e)
  33. {
  34. cout << e.what() << endl;
  35. }
  36. return 0;
  37. }

new也会抛异常的,如果p1这里new 抛异常,这时程序的执行流会直接跳转到主函数中的 catch块中执行,这里没有问题。假设p1没有问题,那如果p2的 new 也抛异常呢?需要再嵌套一个 try、catch??

  1. void Func()
  2. {
  3. int* p1 = new int;
  4. int* p2 = nullptr;
  5. try
  6. {
  7. int* p2 = new int;//p2的new可能会抛异常
  8. try
  9. {
  10. cout << div() << endl;
  11. }
  12. catch (...)
  13. {
  14. delete p1;
  15. delete p2;
  16. throw;
  17. }
  18. }
  19. catch (...)
  20. {
  21. delete p1;
  22. throw;
  23. }
  24. delete p1;
  25. delete p2;
  26. }

这样的代码显得很low,疯狂嵌套try、catch语句,所以为了解决这个问题就出现的智能指针。

补充:内存泄漏分类,C/C++程序中一般我们关心两种方面的内存泄漏:

  • 堆内存泄漏(Heap leak):堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
  • 系统资源泄漏:指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定

内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测工具

1.2 使用智能指针解决

什么是智能指针??

  • 把申请到的内存空间交给了一个 SmartPtr 进行管理
  • 在构造SmartPtr对象时,把需要被管理的内存空间传入SmartPtr 的对象中
  • 在 SmartPtr 对象消亡时,SmartPtr 的析构函数中会自动将管理的内存空间进行释放、
  • 如果出现抛异常的情况,申请的空间会随着 SmartPtr 对象的生命周期而释放,内存泄漏的问题可以得到很好的解决
  • 为了让 SmartPtr 对象能够像原生指针一样使用,还需要对 * ->运算符进行重载

例如:

  1. //智能指针
  2. template<class T>
  3. class SmartPtr
  4. {
  5. public:
  6. SmartPtr(T* ptr)
  7. :_ptr(ptr)
  8. {}
  9. ~SmartPtr()
  10. {
  11. cout << "delete: " << _ptr << endl;
  12. delete _ptr;
  13. }
  14. T& operator*()
  15. {
  16. return *_ptr;
  17. }
  18. T* operator->()
  19. {
  20. return _ptr;
  21. }
  22. private:
  23. T* _ptr;
  24. };
  25. int div()
  26. {
  27. int a, b;
  28. cin >> a >> b;
  29. if (b == 0)
  30. throw invalid_argument("除0错误");
  31. return a / b;
  32. }
  33. void Func()
  34. {
  35. SmartPtr<int> sp1(new int);
  36. SmartPtr<int> sp2(new int);
  37. cout << div() << endl;
  38. }
  39. int main()
  40. {
  41. try
  42. {
  43. Func();
  44. }
  45. catch (exception& e)
  46. {
  47. cout << e.what() << endl;
  48. }
  49. return 0;
  50. }

上面的代码中将申请到的内存空间交给了一个SmartPtr的对象 sp1、sp2进行管理,这样一来,无论程序是正常执行完毕返回了,还是因为某些原因中途返回了,或是因为抛异常返回了,只要SmartPtr对象的生命周期结束就会调用其对应的析构函数,进而完成内存资源的释放。

1.3 智能指针的原理

(1)RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。

这种做法有两大好处:

  1. 不需要显式地释放资源。
  2. 采用这种方式,对象所需的资源在其生命期内始终保持有效

比如:

  1. // 使用RAII思想设计的SmartPtr类
  2. template<class T>
  3. class SmartPtr {
  4. public:
  5. SmartPtr(T* ptr = nullptr)
  6. : _ptr(ptr)
  7. {}
  8. ~SmartPtr()
  9. {
  10. if (_ptr)
  11. delete _ptr;
  12. }
  13. private:
  14. T* _ptr;
  15. };

(2)像指针行为

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

  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. delete _ptr;
  11. }
  12. T& operator*() { return *_ptr; }
  13. T* operator->() { return _ptr; }
  14. private:
  15. T* _ptr;
  16. };

总结一下智能指针的原理:

  1. RAII特性
  2. 重载operator*和opertaor->,具有像指针一样的行为 

但是这样的智能指针还不够完善,会存在智能指针对象拷贝的问题,所以C++出现了不同版本的智能指针

决智能指针对象的拷贝问题:比如上面实现的智能指针 SmartPtr类,如果用一个SmartPtr对象来拷贝构造另一个SmartPtr对象,或是将一个SmartPtr对象赋值给另一个SmartPtr对象,都会导致程序崩溃

  1. int main()
  2. {
  3. SmartPtr<int> sp1(new int);
  4. SmartPtr<int> sp2(sp1); //拷贝构造
  5. SmartPtr<int> sp3(new int);
  6. SmartPtr<int> sp4(new int);
  7. sp3 = sp4; //拷贝赋值
  8. return 0;
  9. }

原因:

  • 编译器默认生成的拷贝构造函数对内置类型完成值拷贝(浅拷贝),因此用sp1拷贝构造sp2后,相当于这sp1和sp2管理了同一块内存空间,当sp1和sp2析构时就会导致这块空间被释放两次。
  • 编译器默认生成的拷贝赋值函数对内置类型也是完成值拷贝(浅拷贝),因此将sp4赋值给sp3后,相当于sp3和sp4管理的都是原来sp3管理的空间,当sp3和sp4析构时就会导致这块空间被释放两次,并且还会导致sp4原来管理的空间没有得到释放

二、C++的智能指针

2.1 std::auto_ptr

C++98版本的库中就提供了 auto_ptr 的智能指针,文档介绍:std::auto_ptr

  1. 头文件:
  2. #include <memory> 

auto_ptr 通过管理权转移的方式解决智能指针的拷贝问题,保证一个资源在任何时刻都只有一个对象在对其进行管理,这时同一个资源就不会被多次释放了

测试代码如下:

  1. int main()
  2. {
  3. std::auto_ptr<int> ap1(new int(1));
  4. std::auto_ptr<int> ap2(ap1);
  5. *ap2 = 10;
  6. std::auto_ptr<int> ap3(new int(1));
  7. std::auto_ptr<int> ap4(new int(2));
  8. ap3 = ap4;
  9. return 0;
  10. }

进行调试查看:

对一个对象的管理权转移后也就意味着,该对象不能再用对原来管理的资源进行访问了,会造成对象悬空,比如上面的 sp1、sp2,继续使用这两个对象程序就会直接崩溃,因此使用 auto_ptr之前必须先了解它的机制,否则程序很容易出问题。

auto_ptr是一个失败设计,很多公司也都明确规定了禁止使用auto_ptr

auto_ptr的简单模拟实现

  1. namespace fy
  2. {
  3. template<class T>
  4. class auto_ptr
  5. {
  6. public:
  7. //RAII
  8. auto_ptr(T* ptr)
  9. :_ptr(ptr)
  10. {}
  11. auto_ptr(auto_ptr<T>& sp)
  12. :_ptr(sp._ptr)
  13. {
  14. sp._ptr = nullptr;// 管理权转移
  15. }
  16. auto_ptr<T>& operator=(auto_ptr<T>& ap)
  17. {
  18. if (this != &ap)// 检测是否为自己给自己赋值
  19. {
  20. // 释放当前对象中资源
  21. if (_ptr)
  22. delete _ptr;
  23. // 转移ap中资源到当前对象中
  24. _ptr = ap._ptr;
  25. ap._ptr = NULL;
  26. }
  27. return *this;
  28. }
  29. ~auto_ptr()
  30. {
  31. if (_ptr)
  32. {
  33. cout << "delete:" << _ptr << endl;
  34. delete _ptr;
  35. }
  36. }
  37. // 像指针一样使用
  38. T& operator*()
  39. {
  40. return *_ptr;
  41. }
  42. T* operator->()
  43. {
  44. return _ptr;
  45. }
  46. private:
  47. T* _ptr;
  48. };
  49. }

2.2 std::unique_ptr

unique_ptr是C++11中引入的智能指针,unique_ptr通过防拷贝的方式解决智能指针的拷贝问题,也就是简单粗暴的防止对智能指针对象进行拷贝,这样也能保证资源不会被多次释放

  1. 头文件:
  2. #include <memory> 

文档介绍:unique_ptr

 测试代码

  1. int main()
  2. {
  3. std::unique_ptr<int> up1(new int(1));
  4. std::unique_ptr<int> up2(up1); //error,不允许拷贝
  5. return 0;
  6. }

编译报错

unique_ptr简单模拟实现 

  1. namespace fy
  2. {
  3. template<class T>
  4. class unique_ptr
  5. {
  6. public:
  7. // RAII
  8. unique_ptr(T* ptr)
  9. :_ptr(ptr)
  10. {}
  11. ~unique_ptr()
  12. {
  13. if (_ptr)
  14. {
  15. cout << "delete:" << _ptr << endl;
  16. delete _ptr;
  17. }
  18. }
  19. // 像指针一样使用
  20. T& operator*()
  21. {
  22. return *_ptr;
  23. }
  24. T* operator->()
  25. {
  26. return _ptr;
  27. }
  28. //防拷贝
  29. unique_ptr(const unique_ptr<T>&sp) = delete;
  30. unique_ptr<T>& operator=(const unique_ptr<T>&sp) = delete;
  31. private:
  32. T* _ptr;
  33. };
  34. }

 2.3 std::shared_ptr

shared_ptr是 C++11中引入的智能指针,shared_ptr通过引用计数的方式解决智能指针的拷贝问题,也就是说shared_ptr支持拷贝

  1. 头文件:
  2. #include <memory>

 文档介绍:shared_ptr

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

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

通过这种引用计数的方式就能支持多个对象一起管理某一个资源,也就是支持了智能指针的拷贝,并且只有当一个资源对应的引用计数减为0时才会释放资源,因此保证了同一个资源不会被释放多次

测试代码:

  1. // 引用计数支持多个拷贝管理同一个资源,最后一个析构对象释放资源
  2. int main()
  3. {
  4. shared_ptr<int> sp1(new int(1));
  5. shared_ptr<int> sp2(sp1);
  6. *sp1 = 2;
  7. *sp2 = 3;
  8. //use_count成员函数,用于获取当前对象管理的资源对应的引用计数
  9. cout << sp1.use_count() << endl;
  10. shared_ptr<int> sp3(new int(1));
  11. shared_ptr<int> sp4(new int(2));
  12. shared_ptr<int> sp5(new int(3));
  13. sp4 = sp3;
  14. sp5 = sp3;
  15. cout << sp3.use_count() << endl;
  16. return 0;
  17. }

运行结果

调试查看

shared_ptr简单模拟实现

  1. 需要增加一个成员变量count,表示智能指针对象管理的资源对应的引用计数
  2. 在构造函数中申请资源,开辟在堆上,并初始化引用计数,设置为1,表示当前只有一个对象在管理这个资源
  3. 在拷贝构造函数中,拷贝一次,需同时将该资源对应的引用计数++
  4. 在拷贝赋值函数中,先将当前对象管理的资源对应的引用计数--(如果减为0则需要释放),然后再与传入对象一起管理它管理的资源,同时需要将该资源对应的引用计数++。
  5. 在析构函数中,将管理资源对应的引用计数 --,如果减为0则需要将该资源释放
  1. namespace fy
  2. {
  3. template <class T>
  4. class shared_ptr
  5. {
  6. public:
  7. // (1)RAII
  8. shared_ptr(T *ptr)
  9. : _ptr(ptr), _pCount(new int(1))
  10. {
  11. }
  12. ~shared_ptr()
  13. {
  14. if (--(*_pCount) == 0)
  15. {
  16. if (_ptr != nullptr)
  17. {
  18. std::cout << "_ptr: " << _ptr << std::endl;
  19. delete _ptr;
  20. _ptr = nullptr;
  21. }
  22. delete _pCount;
  23. _pCount = nullptr;
  24. }
  25. }
  26. shared_ptr(const shared_ptr<T> &sp)
  27. : _ptr(sp._ptr), _pCount(sp._pCount)
  28. {
  29. ++(*_pCount);
  30. }
  31. shared_ptr<T> &operator=(shared_ptr<T> &sp)
  32. {
  33. if (_ptr != sp._ptr) // 管理同一块空间的对象之间无需进行赋值操作
  34. {
  35. if (--(*_pCount) == 0)
  36. {
  37. std::cout << "operator= delete: " << _ptr << std::endl;
  38. delete _ptr;
  39. delete _pCount;
  40. }
  41. _ptr = sp._ptr;
  42. _pCount = sp._pCount;
  43. ++(*_pCount);
  44. }
  45. return *this;
  46. }
  47. // 获取引用计数
  48. int use_count()
  49. {
  50. return *_pCount;
  51. }
  52. // (2)可以像指针一样使用
  53. T &operator*()
  54. {
  55. return *_ptr;
  56. }
  57. T *operator->()
  58. {
  59. return _ptr;
  60. }
  61. private:
  62. T *_ptr; // 管理的资源
  63. int *_pCount; // 管理的资源对应的引用计数
  64. };
  65. }

测试运行

注意:shared_ptr中的引用计数count不能单纯的定义成一个int类型的成员变量,因为这就意味着每个shared_ptr对象都有一个自己的count成员变量,而当多个对象要管理同一个资源时,这几个对象应该用到的是同一个引用计数

2.4 shared_ptr线程安全问题

上面模拟实现的shared_ptr还存在线程安全的问题,由于管理同一个资源的多个对象的引用计数是共享的,因此多个线程可能会同时对同一个引用计数进行自增或自减操作,而自增和自减操作都不是原子操作,因此需要通过加锁来对引用计数进行保护,否则就会导致线程安全问题

所以需要对代码中的 ++ -- 操作进行加锁

修改代码如下:

  1. #pragma once
  2. #include <iostream>
  3. #include <memory>
  4. #include <mutex>
  5. namespace fy
  6. {
  7. template <class T>
  8. class shared_ptr
  9. {
  10. public:
  11. // (1)RAII
  12. shared_ptr(T *ptr)
  13. : _ptr(ptr), _pCount(new int(1)), _mutex(new mutex)
  14. {
  15. }
  16. // 对++操作进行加锁
  17. void Add()
  18. {
  19. _mutex->lock();
  20. (*_pCount)++;
  21. _mutex->unlock();
  22. }
  23. // 对--操作进行加锁
  24. void Release()
  25. {
  26. _mutex->lock();
  27. bool flag = false;
  28. if (--(*_pCount) == 0) // 将管理的资源对应的引用计数--
  29. {
  30. if (_ptr != nullptr)
  31. {
  32. cout << "delete: " << _ptr << endl;
  33. delete _ptr;
  34. delete _pCount;
  35. _ptr = nullptr;
  36. _pCount = nullptr;
  37. flag = true;
  38. }
  39. }
  40. _mutex->unlock();
  41. if (flag == true) // 释放锁
  42. {
  43. delete _mutex;
  44. }
  45. }
  46. ~shared_ptr()
  47. {
  48. Release();
  49. }
  50. shared_ptr(const shared_ptr<T> &sp)
  51. : _ptr(sp._ptr), _pCount(sp._pCount), _mutex(sp._mutex)
  52. {
  53. Add();
  54. }
  55. shared_ptr<T> &operator=(shared_ptr<T> &sp)
  56. {
  57. if (_ptr != sp._ptr) // 管理同一块空间的对象之间无需进行赋值操作
  58. {
  59. Release();
  60. _ptr = sp._ptr;
  61. _pCount = sp._pCount;
  62. _mutex = sp._mutex;
  63. Add();
  64. }
  65. return *this;
  66. }
  67. // 获取引用计数
  68. int use_count()
  69. {
  70. return *_pCount;
  71. }
  72. // (2)可以像指针一样使用
  73. T &operator*()
  74. {
  75. return *_ptr;
  76. }
  77. T *operator->()
  78. {
  79. return _ptr;
  80. }
  81. private:
  82. T *_ptr; // 管理的资源
  83. int *_pCount; // 管理的资源对应的引用计数
  84. mutex *_mutex; // 管理的资源对应的互斥锁
  85. };
  86. }
  • 在Release函数中,当引用计数被减为0时需要释放互斥锁资源,但不能在临界区中释放互斥锁,因为后面还需要进行解锁操作,因此代码中借助了一个flag变量,通过flag变量来判断解锁后释放需要释放互斥锁资源
  • shared_ptr只需要保证引用计数的线程安全问题,而不需要保证管理的资源的线程安全问题
  • 指向堆上资源的线程安全问题是访问的人处理的,智能指针不管,也管不了

智能指针的定制删除器

当智能指针对象的生命周期结束时,所有的智能指针默认都是以delete的方式将资源释放,这是不太合适的,因为智能指针并不是只管理以new方式申请到的内存空间,智能指针管理的也可能是以new[]的方式申请到的空间,或管理的是一个文件指针,以new[]的方式申请到的内存空间必须以delete[]的方式进行释放,而文件指针必须通过调用fclose函数进行释放

定制删除器是通过仿函数、Lambda表达式或函数指针来实现的,这里简单了解一下,定制删除器是实现很复杂。

2.5 std::weak_ptr

shared_ptr存在一个致命的缺陷:循环引用,为了解决这个问题,产生了weak_ptr。 weak_ptr是C++11中引入的智能指针,weak_ptr不是用来管理资源的释放的,weak_ptr 是对 shared_ptr的补充。

  1. 头文件:
  2. #include <memory>

文档介绍:weak_ptr

循环引用问题

shared_ptr的循环引用问题在一些特定的场景下才会产生。

比如定义如下的结点类:在堆上新建了两个节点,并将这两个结点连接起来,最后再释放这两个节点

  1. struct ListNode
  2. {
  3. ListNode* _next;
  4. ListNode* _prev;
  5. int _val;
  6. ~ListNode(){ cout << "~ListNode()" << endl;}
  7. };
  8. int main()
  9. {
  10. //新建节点
  11. ListNode* node1 = new ListNode;
  12. ListNode* node2 = new ListNode;
  13. node1->_next = node2;
  14. node2->_prev = node1;
  15. //释放
  16. delete node1;
  17. delete node2;
  18. return 0;
  19. }

上述程序是没有问题的,两个结点都能够正确释放。为了防止程序中途返回或抛异常等原因导致结点未被释放,我们将这两个结点分别交给两个shared_ptr对象进行管理,这时为了让连接节点时的赋值操作能够执行,就需要把ListNode类中的next和prev成员变量的类型也改为shared_ptr类型

  1. struct ListNode
  2. {
  3. std::shared_ptr<ListNode> _next;
  4. std::shared_ptr<ListNode> _prev;
  5. int _val;
  6. ~ListNode() { cout << "~ListNode()" << endl; }
  7. };
  8. int main()
  9. {
  10. //新建节点
  11. std::shared_ptr<ListNode> node1(new ListNode);
  12. std::shared_ptr<ListNode> node2(new ListNode);
  13. node1->_next = node2;
  14. node2->_prev = node1;
  15. return 0;
  16. }

这时程序运行结束后两个结点都没有被释放,但如果去掉连接结点时的两句代码中的任意一句,那么这两个结点就都能够正确释放,根本原因就是因为这两句连接结点的代码导致了循环引用

循环引用分析:

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

weak_ptr是C++11中引入的智能指针,weak_ptr不是用来管理资源的释放的,它主要是用来解决shared_ptr的循环引用问题的

  • 原理:weak_ptr支持用shared_ptr对象来构造weak_ptr对象,构造出来的weak_ptr对象与shared_ptr对象管理同一个资源,但不会增加这块资源对应的引用计数 

 在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了,原理就是,node1->_next = node2;和node2->_prev = node1;时weak_ptr的_next和_prev不会增加node1和node2的引用计数。

修改代码如下:

  1. struct ListNode
  2. {
  3. std::weak_ptr<ListNode> _next;
  4. std::weak_ptr<ListNode> _prev;
  5. int _val;
  6. ~ListNode() { cout << "~ListNode()" << endl; }
  7. };
  8. int main()
  9. {
  10. //新建节点
  11. std::shared_ptr<ListNode> node1(new ListNode);
  12. std::shared_ptr<ListNode> node2(new ListNode);
  13. cout << node1.use_count() << endl;
  14. cout << node2.use_count() << endl;
  15. node1->_next = node2;
  16. node2->_prev = node1;
  17. cout << node1.use_count() << endl;
  18. cout << node2.use_count() << endl;
  19. return 0;
  20. }

编译运行,资源正常释放

 weak_ptr简单模拟实现

shared_ptr还会提供一个get函数,用于获取其管理的资源,辅助weak_ptr

  1. T* get() const
  2. {
  3. return _ptr;
  4. }

weak_ptr模拟实现代码如下:

  1. weak_ptr支持用shared_ptr对象拷贝构造weak_ptr对象,构造时获取shared_ptr对象管理的资源
  2. weak_ptr支持用shared_ptr对象拷贝赋值给weak_ptr对象,赋值时获取shared_ptr对象管理的资源
  1. template<class T>
  2. class weak_ptr
  3. {
  4. public:
  5. // RAII
  6. weak_ptr()
  7. :_ptr(nullptr)
  8. {}
  9. weak_ptr(const shared_ptr<T>& sp)
  10. :_ptr(sp.get())//weak_ptr支持用shared_ptr对象拷贝构造weak_ptr对象,构造时获取shared_ptr对象管理的资源。
  11. {}
  12. weak_ptr<T>& operator=(const shared_ptr<T>& sp)
  13. {
  14. _ptr = sp.get();//weak_ptr支持用shared_ptr对象拷贝赋值给weak_ptr对象,赋值时获取shared_ptr对象管理的资源
  15. return *this;
  16. }
  17. // (2)可以像指针一样使用
  18. T& operator*()
  19. {
  20. return *_ptr;
  21. }
  22. T* operator->()
  23. {
  24. return _ptr;
  25. }
  26. private:
  27. T* _ptr;
  28. };

三、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中的实现的
  5. boost库是为C++语言标准库提供扩展的一些C++程序库的总称,boost库社区建立的初衷之一就是为C++的标准化工作提供可供参考的实现,比如在送审C++标准库TR1中,就有十个boost库成为标准库的候选方案。

--------------------- END ---------------------- 

  1. 「 作者 」 枫叶先生
  2. 「 更新 」 2023.5.13
  3. 「 声明 」 余之才疏学浅,故所撰文疏漏难免,
  4. 或有谬误或不准确之处,敬请读者批评指正。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/木道寻08/article/detail/804285
推荐阅读
相关标签
  

闽ICP备14008679号