C++ — 智能指针的简单实现以及循环引用问题

http://blog.csdn.net/dawn_sf/article/details/70168930

智能指针

____________________________________________________



今天我们来看一个高大上的东西,它叫智能指针。 哇这个名字听起来都智能的不得了,其实等你了解它你一定会有一点失望的。。。。因为它说白了

就是个管理资源的。智能指针的原理就是管理资源的RALL机制,我们先来简单了解一下

RALL机制:RALL机制便是通过利用对象的自动销毁,使得资源也具有了生命周期,有了自动销毁(自动回收)的功能。RAII全称为Resource 

Acquisition Is Initialization,它是在一些面向对象语言中的一种惯用法。RAII源于C++,在Java,C#,D,Ada,Vala和Rust中也有应用。资源分配

即初始化,定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释

放。RAII要求,资源的有效期与持有资源的对象的生命期严格绑定,即由对象的构造函数完成资源的分配(获取),同时由析构函数完成资源的释放。在

这种要求下,只要对象能正确地析构,就不会出现资源泄露问题。RALL在这里就是简单提一下而已,现在我们来看我们今天的主角智能指针。

 

智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类。它的诞生理由就是,为粗心和懒的设计的,但是这个设计一定不是反人类

的,因为无论你有多厉害只要你是人你总会有犯错误的时候,所以智能指针可以很好地帮助我们,程序员每次 new 出来的内存都要手动 delete。程序

员忘记 delete,流程太复杂,最终导致没有 delete

异常导致程序过早退出,没有执行 delete 的情况并不罕见。其实智能指针只是怕你忘了delete,而专门设置出来的个对象。有没有感觉它顿时不够

智能呢,但是你绝对不能否认它的实用性和重要性。现在我们来看看智能指针的使用吧:


对于编译器来说,智能指针实际上是一个栈对象,并非指针类型,在栈对象生命期即将结束时,智能指针通过析构函数释放有它管理的堆内存。所有智

能指针都重载了“operator->”操作符,直接返回对象的引用,用以操作对象。访问智能指针原来的方法则使用“.”操作符。先抛开智能指针的几个

版本不说,我们先来讲一下它里面的 * 和 -> 是何进行运算符重载的。下面是我定义的一个类,他只是为了实现原生指针的 * 和 -> 功能:


  1. struct AA  
  2. {  
  3.     int a = 10;  
  4.     int b = 20;  
  5. };  
  6. template<class T>  
  7. class A  
  8. {  
  9. public:  
  10.   
  11.     A(T* ptr)  
  12.         :_ptr(ptr)  
  13.     {}  
  14.   
  15.     T* operator->()  
  16.     {  
  17.         return _ptr;  
  18.     }  
  19.   
  20.     T& operator*()  
  21.     {  
  22.         return *_ptr;  
  23.     }  
  24.   
  25.     A(A<T>& ap)  
  26.     {}  
  27.     A<T>& operator=(A<T>& ap)  
  28.     {}  
  29.     ~A()  
  30.     {delete _ptr;}  
  31. protected:  
  32.     T* _ptr;  
  33. };  
  34.   
  35. int main()  
  36. {  
  37.     A<int>ap1(new int);  
  38.     *ap1 = 10;  
  39.     A<AA>ap2(new AA);  
  40.     cout << *ap1 << endl;  
  41.     cout << (ap2->a)<<"  "<<(ap2->b) << endl;  
  42.     return 0;  
  43. }  


请忽略这个粗糙的A类和AA结构体,我们的目的只是实现原生函数的功能,那么我的功能实现了吗?

           

这里结果没有一点问题,那么我们现在的注意点就应该放在这里是如何实现的:









智能指针的三大版本的实现==>



好了前面那些磨人的小妖精终于清理完了,现在我们真真正正的进入主题,智能指针的发展史以及它的常见的三个版本。

                       1.管理权转移   2.简单粗暴的防拷贝  3.引用计数版本

注意这里我只是实现简单的思想,可能写的不是很好,望大家指出帮助我改正错误。

管理权转移==>


这个智能指针是1998应用到VS上的,现在我们来实现第一个,何为管理权转移呢?


现在我列出该思想的实现代码:

  1. template<class T>  
  2. class AutoPtr  
  3. {  
  4. public:  
  5.   
  6.     AutoPtr(T* ptr)  
  7.         :_ptr(ptr)  
  8.     {}  
  9.   
  10.     T* operator->()  
  11.     {  
  12.         return _ptr;  
  13.     }  
  14.   
  15.     T& operator*()  
  16.     {  
  17.         return *_ptr;  
  18.     }  
  19.   
  20.     AutoPtr(AutoPtr<T>& ap)  
  21.     {  
  22.         this->_ptr = ap._ptr;  
  23.         ap._ptr = NULL;  
  24.     }  
  25.     AutoPtr<T>& operator=(AutoPtr<T>& ap)  
  26.     {  
  27.         if (this != &ap)  
  28.         {  
  29.             delete this->_ptr;  
  30.             this->_ptr = ap._ptr;  
  31.             ap._ptr = NULL;  
  32.         }  
  33.         return *this;  
  34.     }  
  35.     ~AutoPtr()  
  36.     {  
  37.         cout << "智能指针爸爸已经释放过空间了" << endl;  
  38.         delete _ptr;  
  39.     }  
  40. protected:  
  41.     T* _ptr;  
  42. };  
  43.   
  44. int main()  
  45. {  
  46.     AutoPtr<int>ap1(new int);  
  47.     *ap1 = 10;  
  48.     AutoPtr<int>ap2(ap1);  
  49.     AutoPtr<int>ap3(ap2);  
  50.     *ap3 = 20;  
  51.     ap2 = ap3;  
  52.     cout << *ap2 <<endl;  
  53.   
  54.     return 0;  
  55. }  

现在我们先看看它使用普通操作时的结果如何:




现在的结果真的太符合我们的预料了,我们要的就是这样的结果,当你还沉浸自己成功的喜悦的时候,这里虽然成功实现了自动释放空间的功能还有指

针的功能,但是看看下面这种情况:我们把main函数内修改成这个样子:

int main()
 {
AutoPtr<int>ap1(new int);
*ap1 = 10;
AutoPtr<int>ap2(ap1);
cout << *ap1 << endl;
return 0;
 }






然后结果。。调试到这一步程序崩溃了,罪魁祸首就是AutoPtr<int>ap2(ap1),这里原因就是ap2完全的夺取了ap1的管理权。然后导致ap1无家可归,

访问它的时候程序就会崩溃。如果在这里调用ap2 = ap1程序一样会崩溃原因还是ap1被彻彻底底的夺走一切,所以这种编程思想及其不符合C++思想,

所以它的设计思想就是有一定的缺陷。所以一般不推荐使用Autoptr智能指针。 使用了也绝对不能使用"="和拷贝构造。历史在发展,所以我们见到接

下来这种想法:  


简单粗暴法(防拷贝)==>


scoped智能指针 属于 boost 库,定义在 namespace boost 中,包含头文件#include<boost/smart_ptr.hpp> 便可以使用。scoped智能指

  AutoPtr智能指针 一样,可以方便的管理单个堆内存对象,特别的是,scoped智能指针 享所有权,避免了 AutoPtr智能指针恼人的几个问

题,它直接就告诉用户我不提供"="和拷贝构造这两个功能,你别用,用了我也让你编不过去。来看它的实现:

  1. template<class T>  
  2. class ScopedPtr  
  3. {  
  4. public:  
  5.     ScopedPtr()  
  6.     {}  
  7.     AutoPtr(T* ptr)  
  8.         :_ptr(ptr)  
  9.     {}  
  10.   
  11.     T* operator->()  
  12.     {  
  13.         return _ptr;  
  14.     }  
  15.   
  16.     T& operator*()  
  17.     {  
  18.         return *_ptr;  
  19.     }  
  20.     ~AutoPtr()  
  21.     {  
  22.         cout << "智能指针爸爸已经释放过空间了" << endl;  
  23.         delete _ptr;  
  24.     }  
  25.   
  26. protected:  
  27.     ScopedPtr(ScopedPtr<T>& s);  
  28.     ScopedPtr<T> operator=(ScopedPtr<T>& s);  
  29. protected:  
  30.     T* _ptr;  
  31. };  

它的意思就是,我根本不会提供拷贝构造 和 "="的功能,他强任他强,我就是这样。他确实解决上一个智能指针的问题,他直接让用户不能使用这个

功能,这个思想确实有点反人类。。由于scoped智能指针独享所有权,当我们真真需要复制智能指针时,需求便满足不了了,如此我们再引入一个智能

指针,专门用于处理复制,参数传递的情况。这便是如下的shared智能指针。


引用计数版本==>


接下来我们看最后一种,也就是我们现在经常用到的shared智能指针,等到智能指针发展到这一步也就很成熟了它已经几乎完美的解决所有功能,因为

它使用了引用计数版本当指向该片资源的*_num变成0的时候,释放该资源.


  1. template<class T>  
  2. class shared  
  3. {  
  4. public:  
  5.     shared(T* ptr)  
  6.         :_ptr(ptr)  
  7.         , _num(new int(1))  
  8.     {  
  9.     }  
  10.     shared(const shared<T>& ap)  
  11.         :_ptr(ap._ptr)  
  12.         , _num(ap._num)  
  13.     {  
  14.         ++(*_num);  
  15.     }  
  16.     shared<T>& operator=(const shared<T>& ap)  
  17.     {  
  18.         if (_ptr != ap._ptr)  
  19.         {  
  20.             Release();  
  21.             _ptr = ap._ptr;  
  22.             _num = ap._num;  
  23.             ++(*_num);  
  24.         }  
  25.         return *this;  
  26.     }  
  27.     T* operator->()  
  28.     {  
  29.         return _ptr;  
  30.     }  
  31.       
  32.     T& operator*()  
  33.     {  
  34.         return *_ptr;  
  35.     }  
  36.     void Release()  
  37.     {  
  38.         if (0 == (--*_num))  
  39.         {  
  40.             cout << "智能指针爸爸帮你释放空间了" << endl;  
  41.             delete _ptr;  
  42.             delete _num;  
  43.             _ptr = NULL;  
  44.             _num = NULL;  
  45.         }  
  46.     }  
  47.     ~shared()  
  48.     {  
  49.         Release();  
  50.     }  
  51. protected:  
  52.     T* _ptr;  
  53.     int* _num;  
  54. };  
  55.   
  56. int main()  
  57. {  
  58.     shared<int>ap1(new int);  
  59.     *ap1 = 2;  
  60.     shared<int>ap2(ap1);  
  61.     cout << *ap2 << endl;  
  62.     shared<int>ap3(new int);  
  63.     ap3 = ap1;  
  64.   
  65. }  

上面就是我实现的简易的shared智能指针,现在我们调用这个智能指针,我们来看看结果:





我们发现它完美的解决了一切功能,这个指针真的算是很完美的思想,不过你再完美也会有瑕疵,要不然也不会boost::weak_ptr的存在,

boost::weak_ptr的存在就是为boost::shared_ptr解决一点点瑕疵的。这个问题藏得极深不会遇到的,但是当你真的遇到的时候,我相信你会

绞尽脑汁的找BUG,还是很难找的。话不多说,现在我们来看下面这个例子:

  1. struct ListNode  
  2. {  
  3.     int _data;  
  4.     shared_ptr<ListNode> _prev;  
  5.     shared_ptr<ListNode> _next;  
  6.   
  7.     ListNode(int x)  
  8.         :_data(x)  
  9.         , _prev(NULL)  
  10.         ,_next(NULL)  
  11.     {}  
  12.     ~ListNode()  
  13.     {  
  14.         cout << "~ListNode" << endl;  
  15.     }  
  16. };  
  17. int main()  
  18. {  
  19.     shared_ptr<ListNode> cur(new ListNode(1));  
  20.     shared_ptr<ListNode> next(new ListNode(2));  
  21.     cur->_next = next;  
  22.     next->_prev = cur;  
  23.     cout << "cur" << "     " << cur.use_count() << endl;  
  24.     cout << "next" << "     " << next.use_count() << endl;  
  25.     return 0;  
  26. }  

现在我们验证shared智能指针的缺陷,就不用我实现的那个了,那个好多功能我都没实现,我们用专家
shared_ptr智能指针,构造两个双向链表里

面的结点,这里这个双向链表可能有一点简陋,但是我们只是需要它的prev和next指针就够了。现在我们运行代码看看会发生什么情况:



现在cur和next指针所管理的结点现在都有两个指针指针管理,然后在这里会发生这样一件事:



循环引用一般都会发生在这种"你中有我,我中有你"的情况里面,这里导致的问题就是内存泄漏,这段空间一直都没有释放,现在很明显引用计数在这

里就不是很合适了,但是shared_ptr除了这里不够完善,其他的地方都是非常有用的东西,所以编写者在这里补充一个week_ptr,接下来我们看最后一

个智能指针week_ptr。


week_ptr==>


weak_ptr是为了配合shared_ptr而引入的一种智能指针,它更像是shared_ptr的一个助手而不是智能指针,为它不具有普通指针的行为,没有重载

operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况.通俗一点讲就是,首先weak_ptr 是专门为shared_ptr 

准备的。现在我们并不能根据内部的引用计数。weak_ptr  boost::shared_ptr 的观察者对象,观察者意味着weak_ptr 只对shared_ptr 进行引用

不改变其引用计数,当被观察的shared_ptr 失效后,相应的weak_ptr 也相应失效,然后它就什么都不管光是个删 , 也就是这里的cur和next在析

构的时候 , 不用引用计数减一 , 直接删除结点就好。这样也就间接地解决了循环引用的问题,当然week_ptr指针的功能不是只有这一个。但是现在

我们只要知道它可以解决循环引用就好。

现在总结一下:

1、在可以使用 boost 库的场合下,拒绝使用 std::auto_ptr,因为其不仅不符合 C++ 编程思想。


2、在确定对象无需共享的情况下,使用 boost::scoped_ptr。


3、在对象需要共享的情况下,使用 boost::shared_ptr。


4、在需要访问 boost::shared_ptr 对象,而又不想改变其引用计数的情况下(循环引用)使用boost::weak_ptr。


5、最后一点,在你的代码中,尽量不要出现 delete 关键字,因为我们有智能指针。



本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383793.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++(静态)(常量)数据进行初始化问题以及静态变量析构

在C11标准以前我们都不可以在类中对数据成员初始化&#xff0c;仅能在构造函数中进行初始化&#xff1a; class A {int a,b; double c; string d;A():a(1),b(2),c(3),d(""){} };在C11标准以后我们可以在类中对非静态成员进行初始化。实际上的机制是在调用构造函数的…

C++this指针的用法

参考博客&#xff1a;https://www.cnblogs.com/zhengfa-af/p/8082959.html 在 访问对象的非静态成员时会隐式传递一个参数&#xff0c;即对象本身的指针&#xff0c;这个指针名为this。 例如&#xff1a; class A {int a1;public:A(){}void GetA(int a){cout<<this-&g…

C++开发者都应该使用的10个C++11特性

http://blog.jobbole.com/44015/ 感谢冯上&#xff08;治不好你我就不是兽医 &#xff09;的热心翻译。如果其他朋友也有不错的原创或译文&#xff0c;可以尝试推荐给伯乐在线。】 在C11新标准中&#xff0c;语言本身和标准库都增加了很多新内容&#xff0c;本文只涉及了一些皮…

C++不能被声明为虚函数

虚函数是为了实现多态&#xff0c;但是显然并不是所有函数都可以声明为虚函数的。 不能被声明为虚函数的函数有两类&#xff1a; 不能被继承的函数不能被重写的函数 因此&#xff0c;这些函数都不能被声明为虚函数 普通函数构造函数 如果构造函数定义为虚函数&#xff0c;则…

类的声明与定义

类的前向声明&#xff1a; class A;在声明之后&#xff0c;定义之前&#xff0c;类A是一个不完全类型&#xff0c;即知道A是一个类&#xff0c;但是不知道包含哪些成员。不完全类型只能以有限方式使用&#xff0c;不能定义该类型的对象&#xff0c;不完全类型只能用于定义指向…

shared_ptr的一些尴尬

http://blog.csdn.net/henan_lujun/article/details/8984543 shared_ptr在boost库中已经有多年了&#xff0c;C11又为其正名&#xff0c;把他引入了STL库&#xff0c;放到了std的下面&#xff0c;可见其颇有用武之地&#xff1b;但是shared_ptr是万能的吗&#xff1f;有没有什…

C++转换构造函数和类型转换函数

参考博客&#xff1a;https://blog.csdn.net/feiyanaffection/article/details/79183340 隐式类型转换 如果不同类型的数据在一起操作的时候编译器会自动进行一个数据类型转换。例如常用的基本数据类型有如下类型转换关系&#xff1a; 转换构造函数 构造函数有且仅有一个参数…

C++总结8——shared_ptr和weak_ptr智能指针

http://blog.csdn.net/wendy_keeping/article/details/75268687 智能指针的提出&#xff1a;智能指针是存储指向动态分配对象指针的类&#xff0c;用于生存期控制。能够确保正确销毁动态分配的内存&#xff0c;防止内存泄露。 1.智能指针的分类&#xff1a; 不带引用计数的智能…

C++析构函数执行顺序

今天发现主程序中有多个对象时析构函数的执行顺序不是对象定义的顺序&#xff0c;而是对象定义顺序反过来。 思考了一下&#xff0c;结合之前继承、成员对象等的析构函数执行的顺序&#xff0c;我觉得析构函数执行的顺序为&#xff1a;构造函数的顺序反过来&#xff0c;可能是…

c++写时拷贝1

http://blog.csdn.net/SuLiJuan66/article/details/48882303 Copy On Write Copy On Write(写时复制)使用了“引用计数”&#xff08;reference counting&#xff09;&#xff0c;会有一个变量用于保存引用的数量。当第一个类构造时&#xff0c;string的构造函数会根据传入的参…

【C++学习笔记五】模板

模板是泛型编程的基础 函数模板 模板定义以关键字template开始&#xff0c;后跟一个模板参数列表。这是一个逗号分隔的一个或多个模板参数的列表。用尖括号包围起来。 模板函数定义的一般形式&#xff1a; template <class type> ret-tye func-name(parameter list) …

【Java学习笔记十】输入输出流

在Java.io包中提供了一系列用于处理输入/输出的流类。从功能上分为两类&#xff1a;输入流和输出流。从六结构上可分为&#xff1a;字节流&#xff08;以字节为处理单位&#xff09;和字符流&#xff08;以字符为处理单位&#xff09;。 字符是由字节组成。在Java中所有字符用…

C++ 写时拷贝 2

什么情况下会用到c中的拷贝构造函数】&#xff1a; 1&#xff09;用已经存在的同类的对象去构造出另一个新的对象 2&#xff09;当函数的形参是类的对象时&#xff0c;这时调用此函数&#xff0c;使用的是值的拷贝&#xff0c;也会调用拷贝构造函数 3&#xff09;当函数的返…

【Java学习笔记十一】图形用户界面

图形用户界面或图形用户接口(Graphical User Interface&#xff0c;GUI)是指采用图形方式,借助菜单、按钮等标准界面元素&#xff0c;用户可以通过鼠标等外设向计算机系统发出指令、启动操作&#xff0c;并将系统运行的结果同样以图形方式显示给用户的技术。 GUI是事件驱动的&…

C++ 写时拷贝 3

http://blog.csdn.net/ljianhui/article/details/22895505 字符串一种在程序中经常要使用到的数据结构&#xff0c;然而在C中却没有字符串这种类型。在C中&#xff0c;为了方便字符串的使用&#xff0c;在STL中提供了一个string类。该类维护一个char指针&#xff0c;并封装和提…

C++类模板实例化条件

&#xff08;我不想了解这个&#xff0c;可是考试要考。。。。 并不是每次使用模板类都会实例化一个类 声明一个类模板的指针和引用不会引起类模板的实例化如果检查这个指针或引用的成员时时&#xff0c;类模板会实例化定义一个对象的时候需要有类的定义&#xff0c;会实例化…

C++ String类写时拷贝 4

http://blog.51cto.com/zgw285763054/1839752 维基百科&#xff1a; 写入时复制&#xff08;英语&#xff1a;Copy-on-write&#xff0c;简称COW&#xff09;是一种计算机程序设计领域的优化策略。其核心思想是&#xff0c;如果有多个调用者&#xff08;callers&#xff09;同时…

C++笔试复习

基础知识点 C中对象数组在定义的时候全部进行实例化&#xff08;与Java不同&#xff0c;Java相当于只是定义了一个指针数组&#xff0c;没有进行实例化&#xff09; 程序的三种基本控制结构是&#xff1a;顺序结构、循环结构、选择结构 一个C程序开发步骤通常包括&#xff1a…

C++函数默认参数

声明是用户可以看到的部分&#xff0c;客户非常信任地使用这个特性&#xff0c;希望得到一定的结果&#xff0c;但是你在实现里使用了不同的缺省值&#xff0c;那么将是灾难性的。因此编译器禁止声明和定义时同时定义缺省参数值。 类的成员函数的参数表在声明时默认参数位于参…

C语言链表各类操作详解

http://blog.csdn.net/pf4919501/article/details/38818335链表概述   链表是一种常见的重要的数据结构。它是动态地进行存储分配的一种结构。它可以根据需要开辟内存单元。链表有一个“头指针”变量&#xff0c;以head表示&#xff0c;它存放一个地址。该地址指向一个元素。…