当前位置:   article > 正文

C++11——智能指针_c++ 智能指针

c++ 智能指针

目录

前言

一.智能指针的原理

        1.1 RAII思想

        1.2 原理

二.智能指针的分类

        2.1 auto_ptr介绍

        2.2 unique_pt介绍

         2.3 shared_ptr介绍


前言

        由于C++没有GC(垃圾回收器),程序员从堆上申请的资源,打开的文件,创建的套接字需要我们手动释放和关闭。

        但是可能会出现两个问题:

  1. 异常安全问题。申请完资源,由于异常需要捕捉,使得执行流不会按顺序进行,导致资源还没有释放和关闭,就跳到别的地方执行了。
  2. 由于需要程序员手动释放和关闭,可能会有忘记释放和关闭的问题。

        这样就会导致资源的泄漏。计算机的资源是有限的,导致我们可以使用的资源越来越少。

        智能指针是针对我们从堆上申请的空间,比如:new和malloc出来的空间。使得智能指针来帮我们释放空间,不需要我们来手动释放了。

一.智能指针的原理

        1.1 RAII思想

        RAII是一种了利用对象生命周期来控制程序资源(如:内存,文件,套接字,互斥量等)的技术。

        在对象构造时获得资源,使得堆资源的控制在对象的生命周期内都有效,最后在对象析构的时候释放资源。

        实际上我们是将资源的管理托管给了一个对象。这样做有两个好处:

  1. 不需要显示的释放资源。
  2. 资源的控制在对象生命周期内都有效。

利用RAII实现一个最简单的智能指针。

  1. #pragma once
  2. #include<iostream>
  3. using namespace std;
  4. template<class T>
  5. class SmatrPrt{
  6. private:
  7. T* _ptr;
  8. public:
  9. //将外面申请的资源,托管给类的成员
  10. SmatrPrt(T* ptr = nullptr)
  11. :_ptr(ptr)
  12. {}
  13. //在对象析构时,自动释放资源
  14. ~SmatrPrt(){
  15. if (_ptr){
  16. cout << "delete ptr" << endl;
  17. delete _ptr;
  18. }
  19. }
  20. };
  21. #include "SmartPtr.h"
  22. void test(){
  23. //将申请的资源托管给了sp对象
  24. //当对象空间释放会调用析构函数,释放资源
  25. SmatrPrt<int> sp(new int);
  26. }
  27. int main(){
  28. test();
  29. system("pause");
  30. return 0;
  31. }

        1.2 原理

        智能指针的原理利用了RAII思想,并且还需要让智能指针具有指针的功能。即,需要重载operator*和operator->的函数。

        之后在使用指针时,只需要像指针一样控制对象即可。

  1. #pragma once
  2. #include<iostream>
  3. using namespace std;
  4. template<class T>
  5. class SmatrPrt{
  6. private:
  7. T* _ptr;
  8. public:
  9. //将外面申请的资源,托管给类的成员
  10. SmatrPrt(T* ptr = nullptr)
  11. :_ptr(ptr)
  12. {}
  13. T& operator*(){
  14. return *_ptr;
  15. }
  16. T* operator->(){
  17. return _ptr;
  18. }
  19. //在对象析构时,自动释放资源
  20. ~SmatrPrt(){
  21. if (_ptr){
  22. cout << "delete ptr" << endl;
  23. delete _ptr;
  24. }
  25. }
  26. };
  27. #include "SmartPtr.h"
  28. void test(){
  29. SmatrPrt<int> sp(new int);
  30. //像指针一样操作对象
  31. *sp = 20;
  32. cout << *sp << endl;
  33. }
  34. int main(){
  35. test();
  36. system("pause");
  37. return 0;
  38. }

总结指针指针:

  1. 利用RAII思想,实现自动释放资源。
  2. 重载operator*和operator->,使其具有和指针一样的行为和功能。

二.智能指针的分类

        上面的代码有一个bug:

原因:

        用sp1拷贝构造sp2,由于是浅拷贝。会使得两个对象的成员变量指向同一块空间。两个对象在析构时,导致一块空间释放了两次。所以程序会奔溃。

随着C++的发展,有三个解决方案,一个方案对应着一种智能指针。

  1. 将指针的管理权转移给另外一个对象。对应C++98的auto_ptr。
  2. 防止拷贝。对应C++11的unique_ptr。
  3. 引用计数。对应C++11的shared_ptr。

注意:智能指针都包含在memory的库中,要使用智能指针必须包含这个库。

        2.1 auto_ptr介绍

        auto_ptr的原理是:将资源的管理权由一个对象转移给另外一个对象。

         但是这样会有一个问题,如果重新访问ap1,就会出现问题。这种方式在实际在编程中用得少。

模拟实现auto_ptr:

注意:赋值防止自己给自己赋值,还需要释放当前对象以前的资源。

  1. template<class T>
  2. class AutoPtr{
  3. private:
  4. T* _ptr;
  5. public:
  6. AutoPtr(T* ptr = nullptr)
  7. :_ptr(ptr)
  8. {}
  9. //拷贝构造
  10. //将ap资源的管理权交当前对象
  11. AutoPtr(const AutoPtr<T>& ap){
  12. _ptr = ap._ptr;
  13. ap._ptr = nullptr;
  14. }
  15. AutoPtr<T>& operator=(const AutoPtr<T>& ap){
  16. //防止自己给自己赋值
  17. if (this != &ap){
  18. //释放之前的资源
  19. if (_ptr){
  20. delete _ptr;
  21. }
  22. _ptr = ap._ptr;
  23. ap._ptr = nullptr;
  24. }
  25. return *this;
  26. }
  27. T& operator*(){
  28. return *_ptr;
  29. }
  30. T* operator->(){
  31. return _ptr;
  32. }
  33. //在对象析构时,自动释放资源
  34. ~AutoPtr(){
  35. if (_ptr){
  36. cout << "delete ptr" << endl;
  37. delete _ptr;
  38. }
  39. }
  40. };

        2.2 unique_pt介绍

        针对auto_ptr的不足,C++11根据自己的一些语法,设计了一种更靠谱的智能指针,unique_ptr。

        原理:直接简单粗暴,将构造和拷贝构造直接禁止编译器默认生成。

 unique_ptr模拟实现: 

  1. #pragma once
  2. #include<iostream>
  3. using namespace std;
  4. template<class T>
  5. class UniquePtr{
  6. private:
  7. //C++98,将拷贝构造和赋值,显示声明,设为私有
  8. //外部无法调用
  9. UniquePtr(UniquePtr<T>& up);
  10. UniquePtr<T>& operator=(UniquePtr<T>& up);
  11. T* _ptr;
  12. public:
  13. //将外面申请的资源,托管给类的成员
  14. UniquePtr(T* ptr = nullptr)
  15. :_ptr(ptr)
  16. {}
  17. //利用C++11语法,禁止编译器默认生成
  18. UniquePtr(UniquePtr<T>& up) = delete;
  19. UniquePtr<T>& operator=(UniquePtr<T>& up) = delete;
  20. T& operator*(){
  21. return *_ptr;
  22. }
  23. T* operator->(){
  24. return _ptr;
  25. }
  26. //在对象析构时,自动释放资源
  27. ~UniquePtr(){
  28. if (_ptr){
  29. cout << "delete ptr" << endl;
  30. delete _ptr;
  31. }
  32. }
  33. };

         2.3 shared_ptr介绍

        shared_ptr原理:在类中增加一个成员变量用来计数。每次调用一次拷贝构造和赋值重载函数,即每增加一个管理者,计数加1。没析构一个管理者对象,计数器减1,直到减为0,才真正释放资源。

  • shared_ptr内部,给每一份资源维护了一个计数器,用来记录该份资源被几个对象共享。
  • 在对象被销毁,也就是调用析构时,说明不在管理该资源,计数器减1。
  • 如果引用计数等于0,说明当前对象是最后一个使用该资源的对象,必须释放资源。
  • 当引用计数不等于0,说明还有对象在管理资源,不能释放资源。

注意:

       shared_ptr只有在构造和赋值的时候计数才会加1。

计数器如何设置:

        由于当前计数器属于同一资源的对象,可以将计数器设计成一个指针。

        不能设计成引用,引用要引用外部变量,需要外部管理。也不能设计成静态成员变量,静态成员变量,属于整个类,如果当前类要同时管理其它资源计数器就乱了。

线程安全问题:

        由于引用了一个计数器。计数器自增(++),自减(--)和判断的行为不是原子的。当多个线程进入,会导致线程安全问题。从而导致资源没有释放,造成内存泄漏。

        为了保证线程安全,可以在shared_ptr类中,再增加一把锁。由于同一份资源的对象要看到同意把锁,所以,锁也需要声明成指针类型。

shared_ptr模拟实现:

        注意:

  1. 在赋值重载函数中,首先需要判断是否是自己给自己赋值;
  2. 其次,不能直接释放被赋值对象的资源,需要先将计数器减1(当前对象不管理了),当计数器等于0,说明没有其它对象管理当前资源,才将资源释放。
  3. 在析构函数中,也是需要将计数器减为0 了,才能将资源释放。
  4. 释放资源时注意,需要先解锁了,再释放锁的资源。
  5. 在计数器自增,自减和判断前需要加锁。

shared_ptr缺陷:

循环引用问题:

一种情况:

         当循环引用时,shared_ptr会出现资源没有释放的问题。

解决方案:

        C++11增加了一个weak_ptr专门来解决shared_ptr循环引用的问题。

        原理是:weak_ptr的成员变量是shared_ptr,不在具有RAII的思想,只是具有指针的功能,也就是,不会释放资源,也不会将计数器计数。

        使用weak_ptr前提是,知道是循环引用了。

  1. template<class T>
  2. class WeakPtr{
  3. private:
  4. T* _wptr;
  5. public:
  6. WeakPtr(T* wptr = nullptr){
  7. _wptr = wptr;
  8. }
  9. //用shared_ptr构造
  10. WeakPtr(const SharedPtr<T>& sp){
  11. _wptr = sp.GetPtr();
  12. }
  13. WeakPtr<T>& operator=(const SharedPtr<T>& sp){
  14. _wptr = sp.GetPtr();
  15. return *this;
  16. }
  17. //具有指针的功能
  18. T* operator->(){
  19. return &_wptr
  20. }
  21. T& operator*(){
  22. return *_wptr;
  23. }
  24. };

 上面问题的改造:

  删除器:

        shared_ptr类里默认的在析构函数里释放资源使用的是delete。但是当我们申请出来的不是一个对象,而是多个对象时:

 于是C++11增加了一个删除器,可以根据申请对象的不同,设计删除动作,传入shared_ptr中。

实际用法是,写一个仿函数类(写一个类,从在operator()函数,函数实现是需要删除资源的动作),实例化对象,传入shared_ptr中。

  1. template<class T>
  2. class Del{
  3. public:
  4. void operator()(T* ptr){
  5. delete[] ptr;
  6. }
  7. };
  8. class DelFle{
  9. public:
  10. void operator()(FILE* ptr){
  11. fclose(ptr);
  12. }
  13. };
  14. void test(){
  15. Del<int> d;
  16. shared_ptr<int> sp1(new int[10], d);
  17. DelFle df;
  18. shared_ptr<FILE> sp2(fopen("text.txt", "w"), df);
  19. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小惠珠哦/article/detail/781545
推荐阅读
相关标签
  

闽ICP备14008679号