zoukankan      html  css  js  c++  java
  • C++智能指针的原理和实现

    一、智能指针起因

      在C++中,动态内存的管理是由程序员自己申请和释放的,用一对运算符完成:new和delete。

      new:在动态内存中为对象分配一块空间并返回一个指向该对象的指针;

      delete:指向一个动态独享的指针,销毁对象,并释放与之关联的内存。

      使用堆内存是非常频繁的操作,容易造成堆内存泄露、二次释放等问题,为了更加容易和更加安全的使用动态内存,C++11中引入了智能指针的概念,方便管理堆内存,使得自动、异常安全的对象生存期管理可行。智能指针主要思想是RAII思想,“使用对象管理资源”,在类的构造函数中获取资源,在类的析构函数中释放资源。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。

      RAII是Resource Acquisition Is Initialization的简称,即资源获取就是初始化:

      1.定义一个类来封装资源的分配与释放;

      2.构造函数中完成资源的分配及初始化;

      3.析构函数中完成资源的清理,可以保证资源的正确初始化和释放;

      4.如果对象是用声明的方式在栈上创建局部对象,那么RAII机制就会正常工作,当离开作用域对象会自动销毁而调用析构函数释放资源。

    二、智能指针类型

      智能指针在C++11版本之后提供,包含在头文件<memory>中,标准命名std空间下,有auto_ptr、shared_ptr、weak_ptr、unique_ptr四种,其中auto_ptr已被弃用。

      auto_ptr:拥有严格对象所有权语义的智能指针;

      shared_ptr:拥有共享对象所有权语义的智能指针;

      weak_ptr:到 shared_ptr 所管理对象的弱引用;

      unique_ptr:拥有独有对象所有权语义的智能指针。

    2.1 auto_ptr

      auto_ptr是通过由 new 表达式获得的对象,并在auto_ptr自身被销毁时删除该对象的智能指针,它可用于为动态分配的对象提供异常安全、传递动态分配对象的所有权给函数和从函数返回动态分配的对象,是一个轻量级的智能指针,适合用来管理生命周期比较短或者不会被远距离传递的动态对象,最好是局限于某个函数内部或者是某个类的内部。

      声明:

      templateclass T class auto_ptr;

      template<> class auto_ptr<void>;  // 对类型void特化  

      成员函数:

      (1) get: 获得内部对象的指针;

      (2) release:释放被管理对象的所有权,将内部指针置为空,返回内部对象的指针,此指针需要手动释放;

      (3) reset:销毁内部对象并接受新的对象的所有权;

      (4) operator=:从另一auto_ptr转移所有权;

      (5) operator*operator->:访问被管理对象。

      注意事项:

      (1) 其构造函数被声明为explicit,因此不能使用赋值运算符对其赋值,即不能使用类似这样的形式 auto_ptr<int> p = new int;

      (2) auto_ptr 的对象所有权是独占性的,使用拷贝构造和赋值操作符时,会造成对象所有权的转移,被拷贝对象在拷贝过程中被修改;

      (3) 基于第二条,因此不能将auto_ptr放入到标准容器中或作为容器的成员;

      (4) auto_ptr不能指向数组,释放时无法确定是数组指针还是普通指针;

      (5) 不能把一个原生指针交给两个智能指针对象管理,对其它智能指针也是如此。

      auto_ptr是最早期的智能指针,C++11 中已被弃用,C++17 中移除,建议使用unique_ptr代替auto_ptr。

      简单实现:

    复制代码
     1 template<class T>
     2 class AutoPointer
     3 {
     4 public:
     5     AutoPointer(T* ptr)
     6       :mPointer(ptr){}
     7 
     8     AutoPointer(AutoPointer<T>& other)
     9     {
    10         mPointer= other.mPointer;  //管理权进行转移
    11         other.mPointer= NULL;
    12     }
    13 
    14     AutoPointer& operator = (AutoPointer<T>& other)
    15     {
    16         if(this != &other)
    17         {
    18             delete mPointer;
    19             mPointer = other.mPointer;  //管理权进行转移
    20             other.mPointer= NULL;
    21         }
    22 
    23         return *this;
    24     }
    25 
    26     ~AutoPointer()
    27     {
    28         delete mPointer;
    29     }
    30 
    31     T& operator * ()
    32     {
    33         return *mPointer;
    34     }
    35 
    36     T* operator -> ()
    37     {
    38         return mPointer;
    39     }
    40 
    41 private:
    42 
    43     T* mPointer;
    44 };
    复制代码

    2.2 shared_ptr

      shared_ptr多个指针指向相同的对象,也叫共享指针。shared_ptr采用了引用计数的方式,更好地解决了赋值与拷贝的问题,每一个shared_ptr的拷贝都指向相同的内存,每拷贝一次内部的引用计数加1,每析构一次内部的引用计数减1,为0时自动删除所指向的堆内存。shared_ptr内部的引用计数是线程安全的,但是对象的读取时需要加锁。

    声明:

      templateclass T class shared_ptr; 

      成员函数:

      (1) get: 获得内部对象的指针;

      (2) swap:交换所管理的对象;

      (3) reset:替换所管理的对象;

      (4) use_count:返回shared_ptr所指对象的引用计数;

      (5) operator*operator->:解引用存储的对象指针;

      (6) operator=:对shared_ptr赋值;

      (7) operator bool:检查是否有关联的管理对象;

      (8) owner_before:提供基于拥有者的共享指针排序。

      交换: std::swap(std::shared_ptr) 特化的swap算法用于交换两个智能指针。

      初始化:通过构造函数传入指针初始化,也可以使用std::make_shared 或 std::allocate_shared 函数初始化。

      注意事项:

      (1) 不能将指针直接赋值给一个智能指针,一个是类,一个是指针。不能使用类似这样的形式 shared_ptr<int> p = new int;

      (2) 避免循环引用,这是shared_ptr的一个最大陷阱,导致内存泄漏,这一点在weak_ptr中将得到完善;

      (3) 管理数组指针时,需要制定Deleter以使用delete[]操作符销毁内存,shared_ptr并没有针对数组的特化版本;

      (4) 不能把一个原生指针交给两个智能指针对象管理,对其它智能指针也是如此。

      简单实现:

    复制代码
     1 template <typename T>
     2 class SharedPointer
     3 {
     4 private:
     5     
     6     class Implement
     7     {
     8     public:
     9         Implement(T* p) : mPointer(p), mRefs(1){}
    10         ~Implement(){ delete mPointer;}
    11         
    12         T* mPointer;  //实际指针
    13         size_t mRefs;  // 引用计数
    14     };
    15     
    16     Implement* mImplPtr;
    17     
    18 public:
    19     
    20     explicit SharedPointer(T* p)
    21       : mImplPtr(new Implement(p)){}
    22         
    23     ~SharedPointer()
    24     {
    25         decrease();  // 计数递减
    26     }
    27     
    28     SharedPointer(const SharedPointer& other)
    29       : mImplPtr(other.mImplPtr)
    30     {
    31         increase();  // 计数递增
    32     }
    33     
    34     SharedPointer& operator = (const SharedPointer& other)
    35     {
    36         if(mImplPtr != other.mImplPtr)  // 避免自赋值
    37         {
    38             decrease();
    39             mImplPtr = other.mImplPtr;
    40             increase();
    41         }
    42         
    43         return *this;
    44     }
    45     
    46     T* operator -> () const
    47     {
    48         return mImplPtr->mPointer;
    49     }
    50     
    51     T& operator * () const
    52     {
    53         return *(mImplPtr->mPointer);
    54     }
    55     
    56 private:
    57     
    58     void decrease()
    59     {
    60         if(--(mImplPtr->mRefs) == 0)
    61         {
    62             delete mImplPtr;
    63         }
    64     }
    65     
    66     void increase()
    67     {
    68         ++(mImplPtr->mRefs);
    69     }
    70 };
    复制代码

    2.3 weak_ptr

      weak_ptr是为了配合shared_ptr而引入的一种智能指针,用于专门解决shared_ptr循环引用的问题,因为它不具有普通指针的行为,没有重载operator * 和 ->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。weak_ptr可以使用一个非常重要的成员函数lock(),从被观测的shared_ptr获得一个可用的shared_ptr对象,从而操作资源。

      声明:

      templateclass T class weak_ptr; 

      成员函数:

      (1) swap:交换所管理的对象;

      (2) reset:替换所管理的对象;

      (3) use_count:返回shared_ptr所指对象的引用计数;

      (4) operator=:对shared_ptr赋值;

      (5) expired:检查被引用的对象是否已删除;

      (6) owner_before:提供基于拥有者的共享指针排序;

      (7) lock:创建管理被引用的对象的shared_ptr。

      交换:std::swap(std::weak_ptr) 特化的swap算法用于交换两个智能指针。

      注意事项:

      (1) 不能将指针直接赋值给一个智能指针,一个是类,一个是指针。不能使用类似这样的形式 shared_ptr<int> p = new int;

      (2) 不能把一个原生指针交给两个智能指针对象管理,对其它智能指针也是如此。

      简单实现:weak_ptr的典型实现存储二个指针,即指向控制块的指针和作为构造来源的shared_ptr的存储指针。

      以下是VC的源码实现:

    复制代码
     1 template<class _Ty>
     2 class weak_ptr
     3     : public _Ptr_base<_Ty>
     4 {    // class for pointer to reference counted resource
     5     typedef typename _Ptr_base<_Ty>::_Elem _Elem;
     6 
     7 public:
     8     weak_ptr()
     9     {    // construct empty weak_ptr object
    10     }
    11 
    12     template<class _Ty2>
    13     weak_ptr(const shared_ptr<_Ty2>& _Other,
    14         typename enable_if<is_convertible<_Ty2 *, _Ty *>::value,
    15         void *>::type * = 0)
    16     {    // construct weak_ptr object for resource owned by _Other
    17         this->_Resetw(_Other);
    18     }
    19 
    20     weak_ptr(const weak_ptr& _Other)
    21     {    // construct weak_ptr object for resource pointed to by _Other
    22         this->_Resetw(_Other);
    23     }
    24 
    25     template<class _Ty2>
    26     weak_ptr(const weak_ptr<_Ty2>& _Other,
    27         typename enable_if<is_convertible<_Ty2 *, _Ty *>::value,
    28         void *>::type * = 0)
    29     {    // construct weak_ptr object for resource pointed to by _Other
    30         this->_Resetw(_Other);
    31     }
    32 
    33     ~weak_ptr()
    34     {    // release resource
    35         this->_Decwref();
    36     }
    37 
    38     weak_ptr& operator=(const weak_ptr& _Right)
    39     {    // assign from _Right
    40         this->_Resetw(_Right);
    41         return (*this);
    42     }
    43 
    44     template<class _Ty2>
    45     weak_ptr& operator=(const weak_ptr<_Ty2>& _Right)
    46     {    // assign from _Right
    47         this->_Resetw(_Right);
    48         return (*this);
    49     }
    50 
    51     template<class _Ty2>
    52     weak_ptr& operator=(shared_ptr<_Ty2>& _Right)
    53     {    // assign from _Right
    54         this->_Resetw(_Right);
    55         return (*this);
    56     }
    57 
    58     void reset()
    59     {    // release resource, convert to null weak_ptr object
    60         this->_Resetw();
    61     }
    62 
    63     void swap(weak_ptr& _Other)
    64     {    // swap pointers
    65         this->_Swap(_Other);
    66     }
    67 
    68     bool expired() const
    69     {    // return true if resource no longer exists
    70         return (this->_Expired());
    71     }
    72 
    73     shared_ptr<_Ty> lock() const
    74     {    // convert to shared_ptr
    75         return (shared_ptr<_Elem>(*this, false));
    76     }
    77 };
    复制代码

    2.4 unique_ptr

      unique_ptr实际上相当于一个安全性增强了的auto_ptr。unique_ptr是通过指针占有并管理另一对象,并在unique_ptr离开作用域时释放该对象的智能指针。unique_ptr的使用标志着控制权的转移,同一时刻只能有一个unique_ptr指向给定对象,通过禁止拷贝语义、只有移动语义来实现。相比与原始指针unique_ptr用于其RAII的特性,使得在出现异常的情况下,动态资源能得到释放。

      声明:

      templateclass T, class Deleter std::default_delete<Tclass unique_ptr;

      templateclass T, class Deleterclass unique_ptr<T[], Deleter>;  // 管理数组指针

      成员函数:

      (1) get: 返回指向被管理对象的指针;

      (2) get_deleter:返回用于析构被管理对象7的删除器;

      (3) swap:交换所管理的对象;

      (4) reset:替换所管理的对象;

      (5) release:返回一个指向被管理对象的指针,并释放所有权;

      (6) operator bool:检查是否有关联的被管理对象;

      (7) operator=:为unique_ptr赋值;

      (8) operator*operator->:解引用存储的对象指针。

      注意事项:

      (1) 不能将指针直接赋值给一个智能指针,一个是类,一个是指针。不能使用类似这样的形式 shared_ptr<int> p = new int;

      (2) 不能把一个原生指针交给两个智能指针对象管理,对其它智能指针也是如此。

      简单实现:

    复制代码
      1 //default deleter for unique_ptr
      2 template<typename T>
      3 struct DefaultDeleter
      4 {
      5     void operator () (T *p)
      6     {
      7         if(p)
      8         {
      9             delete p;
     10             p = NULL;
     11         }
     12     }
     13 };
     14 
     15 template<typename T, typename Deleter = DefaultDeleter<T>>
     16 class unique_ptr
     17 {
     18 public:
     19 
     20     // construct 
     21     unique_ptr(T *pT = NULL);
     22 
     23     // destroy
     24     ~unique_ptr();
     25 
     26 private:
     27 
     28     // not allow copyable
     29     unique_ptr(const unique_ptr &);
     30 
     31     unique_ptr&operator=(const unique_ptr &);
     32 
     33 public:
     34 
     35     // reset 
     36     void reset(T *p);
     37 
     38     // release the own of the pointer
     39     T* release();
     40 
     41     // get the pointer
     42     T* get();
     43 
     44     // convert unique_ptr to bool
     45     operator bool() const;
     46 
     47     // overload for operator *
     48     T& operator * ();
     49 
     50     // overload for operator ->
     51     T* operator -> ();
     52 
     53 private:
     54 
     55     T *m_pT;  //pointer
     56     
     57     Deleter m_deleter;  //deleter
     58     
     59     void del();  //call deleter
     60 };
     61 
     62 
     63 template<typename T, typename Deleter>
     64  unique_ptr<T, Deleter>::unique_ptr(T *pT) :m_pT(pT)
     65 {
     66 
     67 }
     68 
     69 template<typename T, typename Deleter>
     70  unique_ptr<T, Deleter>::~unique_ptr()
     71 {
     72     del();
     73 }
     74 
     75 template<typename T, typename Deleter>
     76  void unique_ptr<T, Deleter>::del()
     77 {
     78     if(*this)
     79     {
     80         m_deleter(m_pT);
     81         m_pT = NULL;
     82     }
     83 }
     84 
     85 template<typename T, typename Deleter>
     86  T* unique_ptr<T, Deleter>::get()
     87 {
     88     return m_pT;
     89 }
     90 
     91 template<typename T, typename Deleter>
     92  void unique_ptr<T, Deleter>::reset(T *p)
     93 {
     94     del();
     95     m_pT = p;
     96 }
     97 
     98 template<typename T, typename Deleter>
     99  T* unique_ptr<T, Deleter>::release()
    100 {
    101     T *p = m_pT;
    102     m_pT = NULL;
    103     return p;
    104 }
    105 
    106 template<typename T, typename Deleter>
    107  unique_ptr<T, Deleter>::operator bool() const
    108 {
    109     return NULL != m_pT;
    110 }
    111 
    112 template<typename T, typename Deleter>
    113  T& unique_ptr<T, Deleter>::operator * ()
    114 { 
    115     return *m_pT;
    116 }
    117 
    118 template<typename T, typename Deleter>
    119  T* unique_ptr<T, Deleter>::operator -> ()
    120 {
    121     return m_pT;
    122 }
    复制代码

    三、总结

      智能指针就是模拟指针动作的类,一般智能指针都会重载 -> 和 * 操作符。智能指针主要作用是管理动态内存的释放。

      1.不要使用std::auto_ptr;

      2.当你需要一个独占资源所有权的指针,且不允许任何外界访问,请使用std::unique_ptr;

      3.当你需要一个共享资源所有权的指针,请使用std::shared_ptr;

      4.当你需要一个能访问资源,但不控制其生命周期的指针,请使用std::weak_ptr;

      5.不能把一个原生指针交给两个智能指针对象管理。

  • 相关阅读:
    spark连接MongoDB
    idea+scala+spark遇到的一些问题
    linux环境变量的配置
    sqoop的导入导出
    hive中一些常用的sql语句
    Unity 插件制作笔记(持续更新)
    linux-shutdown命令说明
    linux中的redis缓存服务器
    IceScrum敏捷开发工具的安装文档-官方最新版
    PHP设计模式系列
  • 原文地址:https://www.cnblogs.com/bruce1992/p/14010704.html
Copyright © 2011-2022 走看看