首页 > 编程语言 >C++智能指针的原理和实现

C++智能指针的原理和实现

时间:2023-06-06 11:32:22浏览次数:65  
标签:weak C++ 智能 operator shared unique ptr 指针

一、智能指针起因

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

  声明:

< class T > class auto_ptr;

<> 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) 不能把一个原生指针交给两个智能指针对象管理,对其它智能指针也是如此。

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内部的引用计数是线程安全的,但是对象的读取时需要加锁。

声明:

template< class T > class shared_ptr; 

  成员函数:

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

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

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

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

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

  (6) operator=:对shared_ptr赋值;

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

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对象,从而操作资源。


template< class 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的特性,使得在出现异常的情况下,动态资源能得到释放。


template< class T, class Deleter = std::default_delete<T> > class;

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

  成员函数:

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

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

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

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

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

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.不能把一个原生指针交给两个智能指针对象管理。

标签:weak,C++,智能,operator,shared,unique,ptr,指针
From: https://blog.51cto.com/u_4018548/6423477

相关文章

  • 【触想智能】人工智能化是国产工控机未来发展的必然趋势
    工控机被称为工业控制计算机,也叫工控主机。它既可以和工业显示器一起使用,也可以单独和其他外设设备使用,应用比较灵活,目前已经广泛应用于工业、商业、金融、国防等各个领域。随着计算机、通信和网络技术的不断发展以及工业互联网的兴起,工控机行业得到了快速发展。而在中国......
  • 智能呼叫中心解决方案:搭建智能呼叫中心系统
    什么是呼叫中心智能化?呼叫中心智能化是指利用人工智能(AI)技术,以实现自动化和优化呼叫中心的业务流程,提高工作效率和用户满意度,降低运营成本。主要涉及的技术和应用有:智能语音机器人:利用语音识别和自然语言处理技术,自动与客户进行交互,处理简单的查询或问题,或预处理并转接到人工座席。......
  • 智能化党务管理信息系统对党建工作的支持和促进
    在党建工作中,智能化党务管理信息系统也逐渐成为了一种重要的辅助工具。本文将介绍智能化党务管理信息系统对党建工作的支持和促进。一、党务管理信息系统的概述党务管理信息系统是指一个完整的系统,包括了党员档案管理、组织架构管理、党费管理、教育培训管理等多个模块。通过该系统......
  • 系统工程(二十)商业智能
    商业智能的核心组成部分是数据仓库、OLAP联机分析、数据挖掘,用途是用于决策分析。数据库与数据仓库的差别:数据仓库是主要用于存储数据,包括历史数据,很少用于数据的删除修改更新,主要用于查询。而数据主要用于CRUD,创建、阅读、更新、删除。数据仓库是面向主题的,主题就是以某个概念......
  • C++ 中的信号的处理
    C++ 信号处理信号是由操作系统传给进程的中断,会提早终止一个程序。在UNIX、LINUX、MacOSX或Windows系统上,可以通过按Ctrl+C产生中断。有些信号不能被程序捕获,但是下表所列信号可以在程序中捕获,并可以基于信号采取适当的动作。这些信号是定义在C++头文件<csignal>中。......
  • C#调用C++dll
    C#调用C++dll的方法和步骤其他分享涉及到的概念和方法对于像我这样比较菜的选手看起来比较费劲并且很难抓住重点,这里我总结了一段时间的研究成果供初学者救济之用,简单明了。工具/原料 VS2008方法/步骤 新建项目->VisualC++->Win32项目 MyDLL注意:C++编写的dll一般是不能直接拿来......
  • C#中调用c++的dll具体创建与调用步骤,亲测有效~ (待验证)
    使用的工具是VS2010哦~其他工具暂时还没试过我新建的工程名是my21dll,所以会生成2个同名文件。接下来需要改动的只有画横线的部分下面是my21dll.h里面的。。。下面的1是自动生成的不用动,或者也可以不要,因为只是一个宏而已下面可以做相应修改。下面的2是自动生成的类,我没用就注释掉了......
  • C++面试八股文:如何在堆上和栈上分配一块内存?
    某日二师兄参加XXX科技公司的C++工程师开发岗位6面:面试官:如何在堆上申请一块内存?二师兄:常用的方法有malloc,new等。面试官:两者有什么区别?二师兄:malloc是向操作系统申请一块内存,这块内存没有经过初始化,通常需要使用memset手动初始化。而new一般伴随三个动作,向操作系统申请一......
  • win10,vs2015深度学习目标检测YOLOV5+deepsort C++多目标跟踪代码实现,源码注释,拿来即
    int8,FP16等选择,而且拿来即用,自己再win10安装上驱动可以立即使用,不用在自己配置,支持答疑。自己辛苦整理的,求大佬打赏一顿饭钱。苦苦苦、平时比较比忙,自己后期会继续发布真实场景项目;欢迎下载。优点:1、架构清晰,yolov5和sort是分开单独写的,可以随意拆解拼接,都是对外接口。2、支持答疑......
  • c++
    因此,想要查看一个数组变量的地址,代码为:intarr[10];cout<<arr<<endl;//注意,arr之前无需&。查看数组中第一个元素的地址:intarr[10];cout<<&arr[0]<<endl;递归:递归的过程是压栈的过程,递归结束,会出栈进行计算二维数组:二维数组的存储是按照行进行扫描,每行的元素依......