• 从C语言到C++_36(智能指针RAII)auto_ptr+unique_ptr+shared_ptr+weak_ptr


    目录

    1. 智能指针的引入_内存泄漏

    1.1 内存泄漏

    1.2 如何避免内存泄漏

    2. RAII思想

    2.1 RAII解决异常安全问题

    2.2 智能指针原理

    3. auto_ptr

    3.1 auto_ptr模拟代码

    4. unique_ptr

    4.1 unique_ptr模拟代码

    5. shared_ptr

    5.1 shared_ptr模拟代码

    5.2 循环引用

    6. weak_ptr

    6.1 weak_ptr模拟代码

    7. 定制删除器(了解)

    8. 完整代码

    9. 笔试面试题

    9.1 智能指针的发展历史

    9.2 笔试选择题:

    9.3 选择题答案及解析

    本篇完。


    1. 智能指针的引入_内存泄漏

    为什么需要智能指针?上一篇:


    1.1 内存泄漏

    上面是异常安全导致的内存泄漏问题,开空间没有释放也可能导致内存泄漏。

    什么是内存泄漏?:

            内存泄漏指因为疏忽或错误(逻辑错误)造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制(指针丢了),因而造成了内存的浪费。

            内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

    1. void MemoryLeaks()
    2. {
    3. int* p1 = (int*)malloc(sizeof(int)); // 1.内存申请了忘记释放
    4. int* p2 = new int;
    5. int* p3 = new int[10]; // 2.异常安全问题
    6. Func(); // 这里如果Func函数抛异常n,会导致下一行 delete[] p3未执行,p3没被释放.
    7. delete[] p3;
    8. }

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


    1.2 如何避免内存泄漏

    • 1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证。
    • 2. 采用RAII思想或者智能指针来管理资源。
    • 3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
    • 4. 出问题了使用内存泄漏工具检测,如Valgrind和Sanitizer。ps:不过很多工具都不够靠谱,或者收费昂贵。

    总结 :内存泄漏非常常见,解决方案分为两种:

    1. 事前预防型。如智能指针等。

    2. 事后查错型。如泄漏检测工具。


    2. RAII思想

    • RAII:是英文Resource Acquisition Is Initialization(资源获取即初始化)的首字母,是一种利用对象生命周期来控制程序资源的简单技术。
    • 这些资源可以是内存,文件句柄,网络连接,互斥量等等。

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

    这种做法有两大好处:
    ① 不需要显式地释放资源。
    ② 采用这种方式,对象所需的资源在其生命期内始终保持有效。

    2.1 RAII解决异常安全问题

    利用RAII思想设计delete资源的类:

    1. #include
    2. using namespace std;
    3. double Division(int a, int b)
    4. {
    5. if (b == 0)
    6. {
    7. throw "Divide by Zero Error";
    8. }
    9. else
    10. {
    11. return ((double)a / (double)b);
    12. }
    13. }
    14. // 利用RAII思想设计delete资源的类
    15. template<class T>
    16. class SmartPtr
    17. {
    18. public:
    19. SmartPtr(T* ptr)
    20. :_ptr(ptr)
    21. {}
    22. ~SmartPtr()
    23. {
    24. cout << "delete: " << _ptr << endl;
    25. delete _ptr;
    26. }
    27. protected:
    28. T* _ptr;
    29. };
    30. void Func()
    31. {
    32. //1、如果p1这里new 抛异常会如何?
    33. //2、如果p2这里new 抛异常会如何?
    34. //3、如果div调用这里又会抛异常会如何?
    35. //int* p1 = new int;
    36. //int* p2 = new int;
    37. //cout << Division() << endl;
    38. //delete p1;
    39. //delete p2;
    40. //cout << "释放资源" << endl;
    41. SmartPtr<int> sp1(new int);
    42. SmartPtr<int> sp2(new int);
    43. cout << Division(3, 0) << endl;
    44. }
    45. int main()
    46. {
    47. try
    48. {
    49. Func();
    50. }
    51. catch (const char* errmsg)
    52. {
    53. cout << errmsg << endl;
    54. }
    55. catch (...)
    56. {
    57. cout << "unknown exception" << endl;
    58. }
    59. cout << "return 0;" << endl;
    60. return 0;
    61. }

    运行:

    把 Division(3, 0) 改为 Division(3, 1):


    2.2 智能指针原理

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

    1. // 1、利用RAII思想设计delete资源的类
    2. // 2、重载operator*和opertaor->,具有像指针一样的行为。
    3. // 3、浅拷贝问题(析构两次,下面讲
    4. template<class T>
    5. class SmartPtr
    6. {
    7. public:
    8. SmartPtr(T* ptr)
    9. :_ptr(ptr)
    10. {}
    11. ~SmartPtr()
    12. {
    13. cout << "delete: " << _ptr << endl;
    14. delete _ptr;
    15. }
    16. T& operator*()
    17. {
    18. return *_ptr;
    19. }
    20. T* operator->()
    21. {
    22. return _ptr;
    23. }
    24. protected:
    25. T* _ptr;
    26. };

            所谓RAII,就是将资源的生命周期和对象的生命周期绑定。从构造函数开始,到析构函数结束。智能指针就是使用了RAII技术,并且利用对象生命周期结束时,编译器会自动调用对象的析构函数来释放资源。

            智能指针的智能就在于资源会被自动释放,不需要显式地释放资源。采用智能指针,对象所需的资源在其生命周期内始终保持有效。

    总结智能指针的原理:

    1、利用RAII思想设计delete资源的类

    2、重载operator*和opertaor->,具有像指针一样的行为。

    3、拷贝问题(不同的智能指针的解决方式不一样)


    3. auto_ptr

    C++98就已经提供了这样的一个智能指针:(注意到上面写着deprecated不推荐使用了)

     让上面写的SmartPtr使用编译器自动生成的拷贝构造函数:

    1. #include
    2. using namespace std;
    3. // 1、利用RAII思想设计delete资源的类
    4. // 2、重载operator*和opertaor->,具有像指针一样的行为。
    5. // 3、浅拷贝问题
    6. template<class T>
    7. class SmartPtr
    8. {
    9. public:
    10. SmartPtr(T* ptr)
    11. :_ptr(ptr)
    12. {}
    13. ~SmartPtr()
    14. {
    15. cout << "delete: " << _ptr << endl;
    16. delete _ptr;
    17. }
    18. T& operator*()
    19. {
    20. return *_ptr;
    21. }
    22. T* operator->()
    23. {
    24. return _ptr;
    25. }
    26. protected:
    27. T* _ptr;
    28. };
    29. int main()
    30. {
    31. SmartPtr<int> sp1(new int);
    32. SmartPtr<int> sp2(sp1);
    33. return 0;
    34. }

    上面代码在运行时报错。

            智能指针ap2拷贝复制了ap1,此时ap1和ap2都指向同一块动态内存空间。当程序执行结束以后,ap1对象和ap2对象都会销毁,并且会执行各自的析构函数,所以那份动态空间就会被释放两次,所以报错了。怎么解决?:
            显式定义一个拷贝构造函数,不能让两个智能指针指向同一份动态内存空间。(但是这样没有很好的解决问题,auto_ptr就是这样设计的

    1. //auto_ptr
    2. #include
    3. using namespace std;
    4. // 1、利用RAII思想设计delete资源的类
    5. // 2、重载operator*和opertaor->,具有像指针一样的行为。
    6. // 3、浅拷贝问题
    7. template<class T>
    8. class SmartPtr
    9. {
    10. public:
    11. SmartPtr(T* ptr)
    12. :_ptr(ptr)
    13. {}
    14. ~SmartPtr()
    15. {
    16. cout << "delete: " << _ptr << endl;
    17. delete _ptr;
    18. }
    19. SmartPtr(SmartPtr& ptr)
    20. :_ptr(ptr._ptr)
    21. {
    22. ptr._ptr = nullptr;
    23. }
    24. T& operator*()
    25. {
    26. return *_ptr;
    27. }
    28. T* operator->()
    29. {
    30. return _ptr;
    31. }
    32. protected:
    33. T* _ptr;
    34. };
    35. int main()
    36. {
    37. SmartPtr<int> sp1(new int);
    38. SmartPtr<int> sp2(sp1);
    39. return 0;
    40. }

    增加一个名字叫A的类重复上面操作:

    1. class A
    2. {
    3. public:
    4. ~A()
    5. {
    6. cout << "~A()" << endl;
    7. }
    8. protected:
    9. int _a1 = 0;
    10. int _a2 = 0;
    11. };
    12. int main()
    13. {
    14. SmartPtr sp1(new A);

    使用一下库里的auto_ptr试一下:

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

    和显式定义一个拷贝构造函数的效果一样。auto_ptr到这种情况就崩了:

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


    3.1 auto_ptr模拟代码

    (上面SmartPtr再加一个赋值重载改下名字就差不多是auto_ptr的模拟了,再用命名空间封一下)

    赋值重载细节还挺多的,前面学的赋值重载都类似拷贝构造,可以不看先写写,这里直接放代码:

    1. #include
    2. #include
    3. using namespace std;
    4. //1、RAII
    5. //2、像指针一样
    6. //3、解决拷贝问题(不同的智能指针的解决方式不一样)
    7. namespace rtx
    8. {
    9. template<class T>
    10. class auto_ptr
    11. {
    12. public:
    13. auto_ptr(T* ptr)
    14. :_ptr(ptr)
    15. {}
    16. ~auto_ptr()
    17. {
    18. cout << "~auto_ptr -> delete: " << _ptr << endl;
    19. delete _ptr;
    20. }
    21. auto_ptr(auto_ptr& ptr)
    22. :_ptr(ptr._ptr)
    23. {
    24. ptr._ptr = nullptr;
    25. }
    26. auto_ptr& operator=(auto_ptr& ap)
    27. {
    28. if (this != &ap) // 防止自己赋值给自己
    29. {
    30. if (_ptr) // 防止释放空,delete空也行
    31. {
    32. cout << "operator= -> Delete:" << _ptr << endl;
    33. delete _ptr;
    34. }
    35. _ptr = ap._ptr;
    36. ap._ptr = nullptr;
    37. }
    38. return *this;
    39. }
    40. T& operator*()
    41. {
    42. return *_ptr;
    43. }
    44. T* operator->()
    45. {
    46. return _ptr;
    47. }
    48. protected:
    49. T* _ptr;
    50. };
    51. }
    52. class A
    53. {
    54. public:
    55. ~A()
    56. {
    57. cout << "~A()" << endl;
    58. }
    59. //protected:
    60. int _a1 = 0;
    61. int _a2 = 0;
    62. };
    63. int main()
    64. {
    65. //SmartPtr sp1(new A);

            可以把命名空间切换到std比较一下,auto_ptr使用的是管理权转移的办法,会导致被拷贝对象悬空,是不负责的拷贝,对于不清楚auto_ptr这个特点的人来说,拷贝后再次使用ap1就会出问题。auto_ptr是C++98一个失败的设计,被挂在了耻辱柱上,很多公司明确要求不能使用auto_ptr。

            C++98至C++11期间人们被迫用C++更新探索的库:boost库里的一些智能指针,到了C++11,终于更新了三个智能指针:unique_prt,shared_ptr,wead_ptr,相当于抄boost库的作业了。下面我们介绍以及模拟实现这几个智能指针,当然,还有很多接口在模拟代码里没有实现。


    4. unique_ptr

    在C++11中更加靠谱的unique_ptr智能指针:

    • unique_ptr直接禁止使用拷贝构造函数,即使编译器也不能生成默认的拷贝构造函数,因为使用了delete关键字。

            unique_ptr采用的策略就是,既然拷贝有问题,那么就直接禁止拷贝,这确实解决了悬空等问题,使得unique_ptr是一个独一无二的智能指针。

    (写到这发现忘记创建新项目了,这里创建一个Test.cpp和SmartPtr.hpp(.h+.cpp,直接.h也行,都可以把函数的实现在里面实现。声明和定义分离只是为了保护源码)


    4.1 unique_ptr模拟代码

    直接复制一份auto_ptr代码过来,用delete关键字禁言拷贝构造和赋值重载就行了:

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

    关于delete关键字的复习链接:(在5.2)从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值_GR_C的博客-CSDN博客


    5. shared_ptr

    unique_ptr禁掉了拷贝,但是如果我就想拷贝智能指针呢?这就要用到shared_ptr了:

             shared_ptr采用了引用计数的方法来解决拷贝问题:(引用计数直接在成员变量加一个int Count可以吗?每一个对象都有一个自己的Count显然是不对的,我们应该让拷贝和被拷贝对象管理同一个Count。那么使用静态成员变量可以吗?这也不可以,因为这样所有的对象都管理的是同一个Count了,包括没有拷贝的对象)

    shared_ptr原理:

    通过引用计数的方式来实现多个shared_ptr对象之间共享资源。
            例如:老师晚上在下班之前都会通知,让最后走的学生记得把门锁下。
    ① shared_ptr内部,给每个资源都维护了一份计数,用来记录该份资源被几个对象共享。
    ② 在对象被销毁时(也就是析构函数调用),说明自己不使用该资源了,对象引用计数减一。
    ③ 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源。
    ④ 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

    shared_ptr增加了一个成员类似int* _pCount解决这个问题:

     这样构造,拷贝构造和析构函数就是这样的:

            构造先给 _pCount指向1,析构无论什么时候都减减,如果减减0就释放资源,拷贝构造就是把指针也给它,然后指针指向的内容加加。

    到这可以自己尝试写一个赋值重载出来,手写或者敲都行OK,这里直接放代码了:

    5.1 shared_ptr模拟代码

    1. template<class T>
    2. class shared_ptr
    3. {
    4. public:
    5. shared_ptr(T* ptr = nullptr)
    6. : _ptr(ptr)
    7. , _pCount(new int(1))
    8. {}
    9. void Release()
    10. {
    11. if (--(*_pCount) == 0) // 防止产生内存泄漏,和析构一样,写成一个函数
    12. {
    13. delete _ptr;
    14. delete _pCount;
    15. }
    16. }
    17. ~shared_ptr()
    18. {
    19. Release();
    20. }
    21. shared_ptr(const shared_ptr& sp)
    22. : _ptr(sp._ptr)
    23. , _pCount(sp._pCount)
    24. {
    25. (*_pCount)++;
    26. }
    27. shared_ptr& operator=(const shared_ptr& sp)
    28. {
    29. //if (this != &sp)
    30. if (_ptr != sp._ptr) // 防止自己给自己赋值,注意不能比较this,类似s1 = s2; 再来一次s1 = s2;
    31. { // 比较_pCount也行
    32. //if (--(*_pCount) == 0) // 防止产生内存泄漏,和析构一样,写成一个函数
    33. //{
    34. // delete _ptr;
    35. // delete _pCount;
    36. //}
    37. Release();
    38. _ptr = sp._ptr;
    39. _pCount = sp._pCount;
    40. (*_pCount)++;
    41. }
    42. return *this;
    43. }
    44. T& operator*()
    45. {
    46. return *_ptr;
    47. }
    48. T* operator->()
    49. {
    50. return _ptr;
    51. }
    52. protected:
    53. T* _ptr;
    54. int* _pCount;// 引用计数,有多线程安全问题,学了linux再讲,不能用静态成员
    55. };

    赋值重载需要注意细节的都在注释写了,可以自己画一个图看看。


    5.2 循环引用

            shared_ptr 完美了吗?并不是,它有一个死穴:循环引用。创建一个链表节点,在该节点的析构函数中打印提示信息:

    1. struct Node
    2. {
    3. ~Node()
    4. {
    5. cout << "~Node" << endl;
    6. }
    7. int _val;
    8. std::shared_ptr _next;
    9. std::shared_ptr _prev;
    10. };

    将n1和n2互相指向,形成循环引用:

    (因为要给_next和_prev赋值,所以Node里也要用智能指针)

    1. int main()
    2. {
    3. std::shared_ptr n1(new Node);
    4. std::shared_ptr n2(new Node);
    5. n1->_next = n2;
    6. n2->_prev = n1;
    7. return 0;
    8. }

    执行该程序后,节点析构函数中的打印信息并没有打印,说明析构出了问题。

    如果不形成循环引用就会打印提示信息:

    可以调用shared_ptr里的use_count接口打印引用计数值:

     

            n1和n2刚创建的时候,它两的引用计数值都是1。当两个节点循环引用后,它们的引用计数值都变成了2。

    n2先析构,右边的引用计数变为1,n1再析构,左边的引用计数变为1,然后就没了。

    左边结点的_next什么时候释放?-> 取决于左边的结点什么时候delete。

    左边的结点什么时候delete?-> 取决于右边结点的_prev。

    右边结点的_prev什么时候释放?-> 取决于右边的结点什么时候delete。

    右边的结点什么时候delete?-> 取决于左边结点的_next。

    左边结点的_next什么时候释放? -> 回到一开始的问题,进入死循环。


    在循环引用中,节点得不到真正的释放,就会造成内存泄漏。

    循环引用的根本原因在于,next和prev也参与了资源的管理

            这个漏洞shared_ptr本身也解决不了,所以就增加了weak_ptr来解决这个问题。解决办法就是让节点中的_next和_prev仅指向对方,而不参与资源管理,也就是计数值不增加。

    这里为了配合上面和给下面模拟weak_ptr演示给我们的shared_ptr加两个接口函数:


    6. weak_ptr

            weak_ptr是为解决循环引用问题而产生的,可以把weak_ptr当作shared_ptr的小跟班,weak_ptr主要用shared_ptr来构造,所以weak_ptr的拷贝构造以及赋值都不会让引用计数值加1,仅仅是指向资源。

    把链表类里的指针换成weak_ptr解决循环引用问题:


    6.1 weak_ptr模拟代码

            weak_ptr中只有一个成员变量_ptr,用来指向动态内存空间,在默认构造函数中,仅仅指向动态内存空间。拷贝构造函数和赋值运算符重载函数中,拷贝和赋值的对象都是shared_ptr指针。
            weak_ptr就是用来解决循环引用问题的,所以拷贝和赋值的智能指针必须是shared_ptr。weak_ptr和shared_ptr并不是同一个类,所以获取shared_ptr中的_ptr时,不能直接访问,需要通过shared_ptr的接口get()来获取。

    1. template<class T> // 辅助型智能指针,配合解决shared_ptr循环引用问题
    2. class weak_ptr // 没有RAII,不管理资源
    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. protected:
    28. T* _ptr;
    29. };

    换下命名空间: 

    效果一样。


    7. 定制删除器(了解)

            前面我们自己实现的所有智能指针中,在释放动态内存资源的时候,都只用了delete,也就是所有new出来的资源都是单个的:

            如果是new int[10],或者malloc(20)呢?当需要释放的资源是其他类型的呢?delete肯定就不能满足了,

    对于不同类型的资源,需要定制删除器。


    先来看库中是如何实现的,这里仅拿shared_ptr为例,unique_ptr也是一样的。

            在构造智能指针的时候,可以传入定制的删除器。可以采用仿函数的方式,lambda的方式以及函数指针的方式,只要是可调用对象都可以。此时的智能指针指向的是动态数组,我们传入的定制删除器也是释放数组的

    1. #include "SmartPtr.hpp"
    2. #include
    3. class A
    4. {
    5. public:
    6. ~A()
    7. {
    8. cout << "~A()" << endl;
    9. }
    10. //protected:
    11. int _a1 = 0;
    12. int _a2 = 0;
    13. };
    14. struct Node
    15. {
    16. ~Node()
    17. {
    18. cout << "~Node" << endl;
    19. }
    20. int _val;
    21. rtx::weak_ptr _next;
    22. rtx::weak_ptr _prev;
    23. };
    24. template<class T>
    25. struct DeleteArray
    26. {
    27. void operator()(T* ptr)
    28. {
    29. cout << "delete[]" << ptr << endl;
    30. delete[] ptr;
    31. }
    32. };
    33. template<class T>
    34. struct Free
    35. {
    36. void operator()(T* ptr)
    37. {
    38. cout << "free" << ptr << endl;
    39. free(ptr);
    40. }
    41. };
    42. int main()
    43. {
    44. // 仿函数对象
    45. std::shared_ptr n1(new Node);
    46. std::shared_ptr n2(new Node[5], DeleteArray());
    47. std::shared_ptr<int> n3(new int[7], DeleteArray<int>());
    48. std::shared_ptr<int> n4((int*)malloc(sizeof(12)), Free<int>());
    49. // lambda
    50. std::shared_ptr n5(new Node);
    51. std::shared_ptr n6(new Node[5], [](Node* ptr) {delete[] ptr; });
    52. std::shared_ptr<int> n7(new int[7], [](int* ptr) {delete[] ptr; });
    53. std::shared_ptr<int> n8((int*)malloc(sizeof(12)), [](int* ptr) {free(ptr); });
    54. //std::shared_ptr n9(fopen("test.txt", "w"), [](FILE* ptr) {fclose(ptr); }); //不安全了,但这样也行
    55. std::unique_ptr> up(new Node[5]); // unique_ptr只能这样传
    56. return 0;
    57. }


            下面我们类似库里unique_ptr的方法给我们的shared_ptr弄个类似的定制删除器:给我们的shared_ptr配一个默认删除方式的仿函数,执行的是delete ptr。

            在shared_ptr类模板的模板参数中增加一个定制删除器的模板参数,缺省值默认删除方式。在释放资源的时候,在Release()中调用定制的删除器仿函数对象。

    1. template<class T>
    2. struct Delete
    3. {
    4. void operator()(T* ptr)
    5. {
    6. cout << "delete:" << ptr << endl;
    7. delete ptr;
    8. }
    9. };
    10. template<class T, class D = Delete>
    11. class shared_ptr
    12. {
    13. public:
    14. shared_ptr(T* ptr = nullptr)
    15. : _ptr(ptr)
    16. , _pCount(new int(1))
    17. {}
    18. void Release()
    19. {
    20. if (--(*_pCount) == 0) // 防止产生内存泄漏,和析构一样,写成一个函数
    21. {
    22. //delete _ptr;
    23. delete _pCount;
    24. //D del;
    25. //del(_ptr);
    26. D()(_ptr); // 对_ptr直接用匿名对象删掉
    27. }
    28. }
    29. ~shared_ptr()
    30. {
    31. Release();
    32. }
    33. ...........................略

    (下面放了这个程序运行的完整代码)

            标准库中的shared_ptr,在使用定制删除器的时候,是在构造对象时传入函数对象来实现的。我们自己实现的shared_ptr,是在实例化时,传入仿函数类型实现的。

            这是因为,C++11标准库实现的方式和我们不一样,它的更加复杂,专门封装了几个类管理引用计数以及定制删除器等内容。


    8. 完整代码

    SmartPtr.hpp:

    1. #include
    2. using namespace std;
    3. //1、RAII
    4. //2、像指针一样
    5. //3、解决拷贝问题(不同的智能指针的解决方式不一样)
    6. namespace rtx
    7. {
    8. template<class T>
    9. class auto_ptr
    10. {
    11. public:
    12. auto_ptr(T* ptr)
    13. :_ptr(ptr)
    14. {}
    15. ~auto_ptr()
    16. {
    17. cout << "~auto_ptr -> delete: " << _ptr << endl;
    18. delete _ptr;
    19. }
    20. auto_ptr(auto_ptr& ptr)
    21. :_ptr(ptr._ptr)
    22. {
    23. ptr._ptr = nullptr;
    24. }
    25. auto_ptr& operator=(auto_ptr& ap)
    26. {
    27. if (this != &ap) // 防止自己赋值给自己
    28. {
    29. if (_ptr) // 防止释放空,delete空也行
    30. {
    31. cout << "operator= -> Delete:" << _ptr << endl;
    32. delete _ptr;
    33. }
    34. _ptr = ap._ptr;
    35. ap._ptr = nullptr;
    36. }
    37. return *this;
    38. }
    39. T& operator*()
    40. {
    41. return *_ptr;
    42. }
    43. T* operator->()
    44. {
    45. return _ptr;
    46. }
    47. protected:
    48. T* _ptr;
    49. };
    50. template<class T>
    51. class unique_ptr
    52. {
    53. public:
    54. unique_ptr(T* ptr)
    55. :_ptr(ptr)
    56. {}
    57. ~unique_ptr()
    58. {
    59. cout << "~unique_ptr -> delete: " << _ptr << endl;
    60. delete _ptr;
    61. }
    62. unique_ptr(unique_ptr& ptr) = delete;
    63. unique_ptr& operator=(unique_ptr& ap) = delete;
    64. T& operator*()
    65. {
    66. return *_ptr;
    67. }
    68. T* operator->()
    69. {
    70. return _ptr;
    71. }
    72. protected:
    73. T* _ptr;
    74. };
    75. template<class T>
    76. struct Delete
    77. {
    78. void operator()(T* ptr)
    79. {
    80. cout << "delete:" << ptr << endl;
    81. delete ptr;
    82. }
    83. };
    84. template<class T, class D = Delete>
    85. class shared_ptr
    86. {
    87. public:
    88. shared_ptr(T* ptr = nullptr)
    89. : _ptr(ptr)
    90. , _pCount(new int(1))
    91. {}
    92. void Release()
    93. {
    94. if (--(*_pCount) == 0) // 防止产生内存泄漏,和析构一样,写成一个函数
    95. {
    96. //delete _ptr;
    97. delete _pCount;
    98. //D del;
    99. //del(_ptr);
    100. D()(_ptr); // 对_ptr直接用匿名对象删掉
    101. }
    102. }
    103. ~shared_ptr()
    104. {
    105. Release();
    106. }
    107. shared_ptr(const shared_ptr& sp)
    108. : _ptr(sp._ptr)
    109. , _pCount(sp._pCount)
    110. {
    111. (*_pCount)++;
    112. }
    113. shared_ptr& operator=(const shared_ptr& sp)
    114. {
    115. //if (this != &sp)
    116. if (_ptr != sp._ptr) // 防止自己给自己赋值,注意不能比较this,类似s1 = s2; 再来一次s1 = s2;
    117. { // 比较_pCount也行
    118. //if (--(*_pCount) == 0) // 防止产生内存泄漏,和析构一样,写成一个函数
    119. //{
    120. // delete _ptr;
    121. // delete _pCount;
    122. //}
    123. Release();
    124. _ptr = sp._ptr;
    125. _pCount = sp._pCount;
    126. (*_pCount)++;
    127. }
    128. return *this;
    129. }
    130. int use_count()
    131. {
    132. return *_pCount;
    133. }
    134. T* get() const
    135. {
    136. return _ptr;
    137. }
    138. T& operator*()
    139. {
    140. return *_ptr;
    141. }
    142. T* operator->()
    143. {
    144. return _ptr;
    145. }
    146. protected:
    147. T* _ptr;
    148. int* _pCount;// 引用计数,有多线程安全问题,学了linux再讲,不能用静态成员
    149. };
    150. template<class T> // 辅助型智能指针,配合解决shared_ptr循环引用问题
    151. class weak_ptr // 没有RAII,不管理资源
    152. {
    153. public:
    154. weak_ptr()
    155. :_ptr(nullptr)
    156. {}
    157. weak_ptr(const shared_ptr& sp)
    158. :_ptr(sp.get())
    159. {}
    160. weak_ptr(const weak_ptr& wp)
    161. :_ptr(wp._ptr)
    162. {}
    163. weak_ptr& operator=(const shared_ptr& sp)
    164. {
    165. _ptr = sp.get();
    166. return *this;
    167. }
    168. T& operator*()
    169. {
    170. return *_ptr;
    171. }
    172. T* operator->()
    173. {
    174. return _ptr;
    175. }
    176. protected:
    177. T* _ptr;
    178. };
    179. }

    Test.cpp:

    1. #include "SmartPtr.hpp"
    2. #include
    3. class A
    4. {
    5. public:
    6. ~A()
    7. {
    8. cout << "~A()" << endl;
    9. }
    10. //protected:
    11. int _a1 = 0;
    12. int _a2 = 0;
    13. };
    14. struct Node
    15. {
    16. ~Node()
    17. {
    18. cout << "~Node" << endl;
    19. }
    20. int _val;
    21. rtx::weak_ptr _next;
    22. rtx::weak_ptr _prev;
    23. };
    24. template<class T>
    25. struct DeleteArray
    26. {
    27. void operator()(T* ptr)
    28. {
    29. cout << "delete[]" << ptr << endl;
    30. delete[] ptr;
    31. }
    32. };
    33. template<class T>
    34. struct Free
    35. {
    36. void operator()(T* ptr)
    37. {
    38. cout << "free" << ptr << endl;
    39. free(ptr);
    40. }
    41. };
    42. int main()
    43. {
    44. 仿函数对象
    45. //std::shared_ptr n1(new Node);
    46. //std::shared_ptr n2(new Node[5], DeleteArray());
    47. //std::shared_ptr n3(new int[7], DeleteArray());
    48. //std::shared_ptr n4((int*)malloc(sizeof(12)), Free());
    49. lambda
    50. //std::shared_ptr n5(new Node);
    51. //std::shared_ptr n6(new Node[5], [](Node* ptr) {delete[] ptr; });
    52. //std::shared_ptr n7(new int[7], [](int* ptr) {delete[] ptr; });
    53. //std::shared_ptr n8((int*)malloc(sizeof(12)), [](int* ptr) {free(ptr); });
    54. std::shared_ptr n9(fopen("test.txt", "w"), [](FILE* ptr) {fclose(ptr); }); //不安全了,但这样也行
    55. //std::unique_ptr> up(new Node[5]); // unique_ptr只能类似这样传
    56. rtx::shared_ptr n1(new Node);
    57. rtx::shared_ptr> n2(new Node[5]);
    58. rtx::shared_ptr<int, DeleteArray<int>> n3(new int[5]);
    59. rtx::shared_ptr<int, Free<int>> n4((int*)malloc(sizeof(12)));
    60. return 0;
    61. }


    9. 笔试面试题

            面试很大几率会让手撕一个指针指针,如果没有要求的话可以写一个uniqeu_ptr,别写auto_ptr就行,有要求的话就应该就是shared_ptr了,所以智能指针的模拟实现应该闭着眼都能手撕出来。

    笔试面试常问问题:

    上面的问题博客上面都讲了,总结下智能指针的发展历史:


    9.1 智能指针的发展历史

            C++98中的auto_ptr,存在非常大的缺陷,在拷贝构造或者赋值的时候,原本的auto_ptr会被置空,所以这个智能指针存在非常大的缺陷,很多地方都禁止使用。
            C++11中的unique_ptr,禁止了拷贝和赋值,直接避免了auto_ptr可能存在的缺陷,是一个独一无二的智能指针,但是它不能拷贝和赋值。
            C++11又提供了shared_ptr,通过引用计数的方式解决了不能拷贝和赋值的缺陷,并且通过互斥锁保证了shared_ptr本身的线程安全,但是它的死穴是循环引用。
            C++11为了解决shared_ptr的循环引用问题,又提供了weak_ptr智能指针,通过仅指向不管理的方式解决了这个问题。
    在使用的时候要根据具体情况选择合适的智能指针,切记最好不要使用auto_ptr。

            C++委员会还发起了一个库,叫boost库,这个库可以理解为C++标准库的先行版,boost库中好用的东西会被C++标准库收录,标准库中的智能指针就是参照boost库中的智能指针再加以修改定义出来的。

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

    ① C++ 98 中产生了第一个智能指针auto_ptr。

    ② C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr。

    ③ C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。

    ④ C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost 的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。


    9.2 笔试选择题:

    1. 以下那个误操作不属于资源泄漏()

    A.打开的文件忘记关闭

    B.malloc申请的空间未通过free释放

    C.栈上的对象没有通过delete销毁

    D.内存泄漏属于资源泄漏的一种,但资源泄漏不仅仅是内存泄漏

    2. 下面那个说法可以表示资源泄漏()

    A.从商店买东西

    B.借钱不还

    C.买房子交首付

    D.办信用卡

    3. 下面关于内存泄漏的说法正确的是()

    A.如果对程序影响不是很大的情况下,泄漏一两个字节不是很重要

    B.内存没有释放时,进程在销毁的时候会统一回收,不用担心

    C.内存泄漏不一定会对系统马上造成影响,可以不着急进行处理

    D.写代码时要有良好的编码规范,万一发生内存泄漏要及时处理

    4. 关于RAII下面说法错误的是()

    A.RAII的实现方式就是在构造函数中将资源初始化,在析构函数中将资源清理掉

    B.RAII方式管理资源,可以有效避免资源泄漏问题

    C.所有智能指针都借助RAII的思想管理资源

    D.RAII方式管理锁,有些场景下可以有效避免死锁问题

    5. 下面关于auto_ptr的说法错误的是()

    A.auto_ptr智能指针是在C++98版本中已经存在的

    B.auto_ptr的多个对象之间,不能共享资源

    C.auto_ptr的实现原理是资源的转移

    D.auto_ptr完全可以正常使用

    6. 下面关于unique_ptr说法错误的是()

    A.unique_ptr是C++11才正式提出的

    B.unique_ptr可以管理一段连续空间

    C.unique_ptr不能使用其拷贝构造函数

    D.unique_ptr的对象之间不能相互赋值


    7. 下面关于shared_ptr说法错误的是 ( )

    A.shared_ptr是C++11才正式提出来的

    B.shared_ptr对象之间可以共享资源

    C.shared_ptr可以应用于任何场景

    D.shared_ptr是借助引用计数的方式实现的

    8. 下面关于weak_ptr的说法错误的是()

    A.weak_ptr与shread_ptr的实现方式类似,都是通过引用计数的方式实现的

    B.weak_ptr的对象可以独立管理资源

    C.weak_ptr的唯一作用就是解决shared_ptr中存在的循环引用问题

    D.weak_ptr一般情况下都用不到


    9.3 选择题答案及解析

    1. C

    A:属于,打开的文件用完时一定要关闭

    B:属于,堆上申请的空间,需要用户显式的释放

    C:不属于,栈上的对象不需要释放,函数结束时编译器会自动释放

    D:正确,资源泄漏包含的比较广泛,比如文件未关闭、套接字为关闭等

    2. B                

    从系统中动态申请的资源,一定要记着及时归还,否则别人可能就使用不了,或者申请失败。就像借钱一样

    3. D

    A:错误,一两个字节不处理时可能会因小失大,很多个两字节,就是很大的一块内存空间

    B:错误,虽然进程退出时会回收,但是进程为退出时可能会影响程序性能甚至会导致崩溃

    C:错误,只要发现了就要及时处理,否则,说不定什么时候程序就会崩溃

    D:正确,内存泄漏最好不要发生,万一发生了一定要及时处理

    4. C

    C:错误,weak_ptr不能单独管理资源,必须配合shared_ptr一块使用,解决shared_ptr中存在的 循环引用问题

    5. D

    A:正确

    B:正确,因为auto_ptr采用资源管理权转移的方式实现的,比如:用ap1拷贝构造ap2时,ap1中 的资源会转移给     ap2,而ap1与资源断开联系

    C:正确

    D:错误,可以使用,但是不建议用,因为有缺陷,标准委员会建议:什么情况下都不要使用auto_ptr

    6. B

    A:正确

    B:错误,C++11中提供的智能指针都只能管理单个对象的资源,没有提供管理一段空间资源的智能指针

    C:正确,因为unique_ptr中已经将拷贝构造函数和赋值运算符重载delete了

    D:正确,原因同C

    7. C

    C:错误,有些场景下shared_ptr可能会造成循环引用,必须与weak_ptr配合使用

    8. B

    A:正确,weak_ptr和shared_ptr都是通过引用计数实现,但是在底层还是有区别的

    B:错误,weak_ptr不能单独管理资源,因为其给出的最主要的原因是配合shared_ptr解决其循环引用问题

    C:正确,处理解决shared_ptr的循环引用问题外,别无它用

    D:正确


    本篇完。

            shared_ptr还有线程安全问题没办法讲,需要学完Linux多线程后再讲。C++一些关于Linux的内容更新到Linux多线程的内容后后再放出来,应该一两篇就够了。

            继承和多态是C++第一重要的话,智能指针应该算是C++第二重要的部分了,后几篇算是C++收尾了。下一篇:特殊类设计和C++的类型转化。

    穿越回来复习顺便贴个下篇链接:​​​​​​​

    从C语言到C++_37(特殊类设计和C++类型转换)单例模式-CSDN博客

  • 相关阅读:
    LNMP搭建
    xcode常用功能与操作
    HP E1740A 模拟量输入模块
    RK3399平台开发系列讲解(基础篇)Linux延迟工作机制 - 视频课
    vue-vuetify-admin案例讲解
    MyBatis(下)
    打车APP开发平台具体解决了哪些出行问题?
    Postgresql源码(77)plpgsql中参数传递和赋值
    14 C++ 二叉树创建 1 头文件 The binary tree creates1 header file
    如何在 Android 中录制屏幕内容,并以H.264数据流形式发送(屏幕广播)
  • 原文地址:https://blog.csdn.net/GRrtx/article/details/132303191