智能指针!

简介: 智能指针!

智能指针

干什么用的?

将智能指针封装成类,利用类对象释放时调用析构函数的特点,将资源交给智能指针管理

利用对象生命周期控制资源——RAII

如何过渡到下面这个问题的?

智能指针的拷贝,默认是浅拷贝,即多个智能指针会指向同一份资源。这是没有问题的,符合智能指针的定义

这样做的问题是,同份资源会被多次析构

为了解决智能指针拷贝(拷贝构造、赋值拷贝)导致的多次析构的问题,衍生出了如下3种智能指针

auto_ptr  1

任何时候只允许一个智能指针对象对一份资源做管理

模拟实现auto_ptr

  template<class T>
  class auto_ptr {
  public:
    //构造
    auto_ptr(T* ptr=nullptr):_ptr(ptr){}
    //拷贝构造
    auto_ptr(auto_ptr<T>& ap)
    {
      _ptr = ap._ptr;
      ap._ptr = nullptr;
    }
    //赋值拷贝 需要考虑特殊情况
    auto_ptr& operator=(auto_ptr& ap)
    {
      delete _ptr;
      _ptr = ap._ptr;
      ap._ptr = nullptr;
      return *this;
    }
    //析构函数
    ~auto_ptr()
    {
      if (_ptr != nullptr)
      {
        delete _ptr;
        _ptr = nullptr;
      }
 
    }
    //->
    T* operator->()
    {
      return _ptr;
    }
    //*
    T& operator* ()
    {
      return *_ptr;
    }
  private:
    T* _ptr;
  };
}

int* a =nullptr;

delete a 为什么不会报错

因为 C++ 标准规定对空指针进行删除操作时,会忽略这个操作,不会引发运行时错误。

int b=0;

int *a =&b

delete a 为什么会报错

因为delete是用于释放new出来的堆空间(动态分配出来的空间),而a指向的空间属于栈上的空间

比较拷贝构造与赋值拷贝之间的差异

为什么要delete _ptr?

因为一个auto_ptr对象只能管理一个资源,所以要先释放自身管理的资源

为什么要这样做?

因为一个资源只能被一个auto_ptr对象管理。

为什么不delete ap._ptr,因为该auto_ptr还有可能被赋值

特殊情况考虑

2个auto_ptr对象的_ptr都为nullptr(表明这两个对象没有管理任何资源)

就会出现delete nullptr的情况,所以要在构造函数部分加判空判断

int* a =nullptr;

delete a 为什么不会报错

因为 C++ 标准规定对空指针进行删除操作时,会忽略这个操作,不会引发运行时错误。

int b=0;

int *a =&b

delete a 为什么会报错

因为delete是用于释放new出来的堆空间(动态分配出来的空间),而a指向的空间属于栈上的空间

为什么拷贝构造没有delete _ptr

因为管理资源的对象还没有被创建出来,也就是说该对象还没有管理任何资源

总结:拷贝构造与赋值拷贝

相同点就是:它们都要保证一个资源只能被一个对象管理

不同点就是:拷贝构造是一个已存在的对象拷贝给一个尚未存在的对象,所以不用释放

                     赋值拷贝是两个已存在的对象间的拷贝,所以要释放

unique_ptr 0

防拷贝,也就是说该智能指针禁止拷贝

具体就是将拷贝构造与赋值构造这两个函数禁用

template <class T>
  class unique_ptr {
  public:
    //默认构造 表示没有管理任何资源
    unique_ptr(T* ptr = nullptr) :_ptr(ptr) {}
    //析构函数
    ~unique_ptr()
    {
      if (_ptr != nullptr)
      {
        delete _ptr;
        _ptr = nullptr;
      }
 
    }
    //->
    T* operator->()
    {
      return _ptr;
    }
    //*
    T& operator* ()
    {
      return *_ptr;
    }
    unique_ptr(unique_ptr<T>& up) = delete;
    unique_ptr& operator=(unique_ptr<T>& up) = delete;
  private:
    T* _ptr;
  };

shared_ptr  >1

允许拷贝,利用计数器管理使用同一份资源的智能指针,当计数器等于0时才释放智能指针管理的资源

这意味着什么?

这意味着可以有多个shared_ptr对象管理同一份资源

  template<class T>
  class shared_ptr {
  public:
    //构造
    shared_ptr(T* ptr=nullptr):_ptr(ptr),_pcount(new int(1)){}
    //拷贝构造
    shared_ptr(shared_ptr& sp)
    {
      _ptr = sp._ptr;
      _pcount = sp._pcount;
      *_pcount++;
    }
    //赋值构造
    shared_ptr& operator=(shared_ptr& sp)
    {
      if (sp._ptr != _ptr)
      {
        if (-- ( * _pcount) == 0)
        {
          delete _ptr;
          delete _pcount;
        }
        _ptr = sp._ptr;
        _pcount = sp._pcount;
        (* _pcount)++;
      }
      return *this;
    }
    //析构
    ~shared_ptr()
    {
      if (--(*_pcount) == 0)
      {
        if (_ptr != nullptr)//特殊情况,_ptr有可能指向空,就对他做处理
        {
          delete _ptr;
          _ptr = nullptr;
        }
        delete _pcount;
        _pcount = nullptr;
      }
    }
    //->
    T* operator->()
    {
      return _ptr;
    }
    //*
    T& operator* ()
    {
      return *_ptr;
    }
    //引用计数
    int use_count()
    {
      return *(_pcount);
    }
  private:
    T* _ptr;
    int* _pcount;
  };
}

循环引用问题

shared_ptr存在循环引用问题,如下例

将ListNode交给shared_ptr管理

运行结果  没有如预期结果释放资源

原因:

程序运行时

程序 结束后

为什么shared_ptr<ListNode>_next 与shared_ptr<ListNode>_prev不在程序结束的时候释放呢?因为它们是new出来的,需要手动释放

weak_ptr

解决循环引用

上述问题的关键其实就是多了一个shared_ptr,导致程序结束时计数器不为0

weak_ptr 接受一个shared_ptr对象 但是计数器不+1

实现:

  template<class T>
  class weak_ptr {
  public:
    //构造
    weak_ptr(T* ptr=nullptr):_ptr(ptr){}
    //拷贝构造
    weak_ptr(shared_ptr<T>& sp)
    {
      if(sp.use_count!=0)
      _ptr = sp.get();
    }
    //赋值拷贝
    weak_ptr& operator=(shared_ptr<T>& sp)
    {
      if (sp.use_count() != 0)
      {
        _ptr = sp.get();
      }
      return *this;
    }
    //析构
    ~weak_ptr()
    {
      _ptr = nullptr;
    }
    //->
    T* operator->()
    {
      return _ptr;
    }
    //*
    T& operator* ()
    {
      return *_ptr;
    }
  private:
    T* _ptr;
  };
}


相关文章
|
监控 Java
如何使用VisualVM分析内存泄漏?具体流程看这里
如何使用VisualVM分析内存泄漏?具体流程看这里
1678 1
|
机器学习/深度学习 人工智能 数据可视化
【好物分享】onnx-modifier:可视化操作助力模型更改,让你不再为更改模型烦恼!
【好物分享】onnx-modifier:可视化操作助力模型更改,让你不再为更改模型烦恼!
2173 0
【好物分享】onnx-modifier:可视化操作助力模型更改,让你不再为更改模型烦恼!
|
存储 人工智能 大数据
Huggingface又上不去了?这里有个新的解决方案!
AI开发者都知道,HuggingFace是一个高速发展的社区,包括Meta、Google、Microsoft、Amazon在内的超过5000家组织机构在为HuggingFace开源社区贡献代码、数据集和模型。
|
Rust 安全 编译器
Rust中的生命周期与借用检查器:内存安全的守护神
本文深入探讨了Rust编程语言中生命周期与借用检查器的概念及其工作原理。Rust通过这些机制,在编译时确保了内存安全,避免了数据竞争和悬挂指针等常见问题。我们将详细解释生命周期如何管理数据的存活期,以及借用检查器如何确保数据的独占或共享访问,从而在不牺牲性能的前提下,为开发者提供了强大的内存安全保障。
|
监控 Java Spring
AOP 是什么?一文带你彻底搞懂面向切面编程
本文带你深入理解AOP(面向切面编程),通过Spring Boot实战实现日志、异常、性能监控等通用功能的统一处理。无需修改业务代码,5步完成方法日志切面,解耦横切关注点,提升代码可维护性,真正实现无侵入式增强。
389 5
|
6月前
|
人工智能 安全 算法
Go入门实战:并发模式的使用
本文详细探讨了Go语言的并发模式,包括Goroutine、Channel、Mutex和WaitGroup等核心概念。通过具体代码实例与详细解释,介绍了这些模式的原理及应用。同时分析了未来发展趋势与挑战,如更高效的并发控制、更好的并发安全及性能优化。Go语言凭借其优秀的并发性能,在现代编程中备受青睐。
223 33
|
数据采集 人工智能 数据挖掘
Python编程入门:从基础到实战的快速指南
【9月更文挑战第25天】本文旨在为初学者提供一个简明扼要的Python编程入门指南。通过介绍Python的基本概念、语法规则以及实际案例分析,帮助读者迅速掌握Python编程的核心技能。文章将避免使用复杂的专业术语,而是采用通俗易懂的语言和直观的例子来阐述概念,确保内容的可读性和实用性。
|
数据采集 人工智能 PyTorch
极智AI | 昇腾CANN ATC模型转换
大家好,我是极智视界,本文介绍一下 昇腾 CANN ATC 模型转换。
775 0
|
机器学习/深度学习 自然语言处理 知识图谱
|
运维 Docker 容器
如何在 Docker 中删除镜像、容器和卷?
如何在 Docker 中删除镜像、容器和卷?
3666 0
如何在 Docker 中删除镜像、容器和卷?