【转】 C++易混知识点4: 自己编写一个智能指针(Reference Counting)学习auto_ptr和reference counting

这篇文章建大的介绍了如何编写一个智能指针。

介绍: 
什么是智能指针?答案想必大家都知道,智能指针的目的就是更好的管理好内存和动态分配的资源,智能指针是一个智能的指针,顾名思义,他可以帮助我们管理内存。不必担心内存泄露的问题。实际上,智能指针是一个行为类似于指针的类,通过这个类我们来管理动态内存的分配和销毁。方便客户端的使用。相比于一般指针,智能指针主要体现在它使用的容易和便捷性。

转载请注明出处: http://blog.csdn.net/elfprincexu

使用一般指针的问题:

一般情况下我们使用指针的问题是什么?答案是内存管理,简单来看下面的一个例子:

  1. char* pName  = new char[1024];
  2. SetName(pName);
  3. if(null != pName)
  4. {
  5. delete[] pName;
  6. }

在上面一段代码中,我们会遇到bug呢? 很有可能在分配内存的时候就出错了,有可能在被调用的时候指针误操作了,也有可能在其他地方操作了。答案太多太多了

我们还是从一个实际的例子开始吧,首先看下面的例子:

  1. class Person
  2. {
  3. int age;
  4. char* pName;
  5. public:
  6. Person(): pName(0),age(0){}
  7. Person(char* pName, int age): pName(pName), age(age){}
  8. ~Person(){}
  9. void Display()
  10. {
  11. printf("Name = %s Age = %d \n", pName, age);
  12. }
  13. void Shout()
  14. {
  15. printf("Ooooooooooooooooo",);
  16. }
  17. };

现在,我们开始使用这个类

  1. void main()
  2. {
  3. Person* pPerson  = new Person("Scott", 25);
  4. pPerson->Display();
  5. delete pPerson;
  6. }

我们可以看到,每次我们新建一个person空间,都要对内存释放,否则就有可能造成内存泄露。

那我们能不能想象一下,存在一个类似指针的类来帮助我们管理内存。

  1. template < typename T > class SP
  2. {
  3. private:
  4. T*    pData; // Generic pointer to be stored
  5. public:
  6. SP(T* pValue) : pData(pValue){}
  7. ~SP()
  8. {
  9. delete pData;
  10. }
  11. T& operator* ()
  12. {
  13. return *pData;
  14. }
  15. T* operator-> ()
  16. {
  17. return pData;
  18. }
  19. };
  20. void main()
  21. {
  22. SP<PERSON> p(new Person("Scott", 25));
  23. p->Display();
  24. // Dont need to delete Person pointer..
  25. }

通过泛型编程,我们可以使用任何类型的指针,但是上面还不是完美,考虑一下下面的案例

  1. void main()
  2. {
  3. SP<PERSON> p(new Person("Scott", 25));
  4. p->Display();
  5. {
  6. SP<PERSON> q = p;
  7. q->Display();
  8. // Destructor of Q will be called here..
  9. }
  10. p->Display();
  11. }

我们发现,p和q指向同一个实例,由于SP类没有定义拷贝函数,系统自动生成一个默认的拷贝函数,实现的是浅赋值,该内存空间被释放了两次!

所以,我们引入Reference Counting的智能指针至关重要,通过对实例被引用的次数来决定该实例是否需要被释放。

  1. class RC
  2. {
  3. private:
  4. int count; // Reference count
  5. public:
  6. void AddRef()
  7. {
  8. // Increment the reference count
  9. count++;
  10. }
  11. int Release()
  12. {
  13. // Decrement the reference count and
  14. // return the reference count.
  15. return --count;
  16. }
  17. };

现在我们有了一个RC类,这个类只做被引用的次数,唯一的数据成员就是用来跟踪被引用的次数。

结合我们刚才的SP类,我们稍作改动

  1. template < typename T > class SP
  2. {
  3. private:
  4. T*    pData;       // pointer
  5. RC* reference;     // Reference count
  6. public:
  7. SP() : pData(0), reference(0)
  8. {
  9. // Create a new reference
  10. reference = new RC();
  11. // Increment the reference count
  12. reference->AddRef();
  13. }
  14. SP(T* pValue) : pData(pValue), reference(0)
  15. {
  16. // Create a new reference
  17. reference = new RC();
  18. // Increment the reference count
  19. reference->AddRef();
  20. }
  21. SP(const SP<T>& sp) : pData(sp.pData), reference(sp.reference)
  22. {
  23. // Copy constructor
  24. // Copy the data and reference pointer
  25. // and increment the reference count
  26. reference->AddRef();
  27. }
  28. ~SP()
  29. {
  30. // Destructor
  31. // Decrement the reference count
  32. // if reference become zero delete the data
  33. if(reference->Release() == 0)
  34. {
  35. delete pData;
  36. delete reference;
  37. }
  38. }
  39. T& operator* ()
  40. {
  41. return *pData;
  42. }
  43. T* operator-> ()
  44. {
  45. return pData;
  46. }
  47. SP<T>& operator = (const SP<T>& sp)
  48. {
  49. // Assignment operator
  50. if (this != &sp) // Avoid self assignment
  51. {
  52. // Decrement the old reference count
  53. // if reference become zero delete the old data
  54. if(reference->Release() == 0)
  55. {
  56. delete pData;
  57. delete reference;
  58. }
  59. // Copy the data and reference pointer
  60. // and increment the reference count
  61. pData = sp.pData;
  62. reference = sp.reference;
  63. reference->AddRef();
  64. }
  65. return *this;
  66. }
  67. };

接下来我们看下客户端调用情况

  1. void main()
  2. {
  3. SP<PERSON> p(new Person("Scott", 25));
  4. p->Display();
  5. {
  6. SP<PERSON> q = p;
  7. q->Display();
  8. // Destructor of q will be called here..
  9. SP<PERSON> r;
  10. r = p;
  11. r->Display();
  12. // Destructor of r will be called here..
  13. }
  14. p->Display();
  15. // Destructor of p will be called here
  16. // and person pointer will be deleted
  17. }

接下来,我们着重分析下上面的调用情况:

1. SP<PERSON> p(new Person("Scott",25));
当我们创建一个新的智能指针的时候,他的参数类型为Person, 参数为一个Person的普通指针, 智能指针p中的情况是
构造函数被调用,pData 复制新创建的person指针, 同时新建一个RC成员,同时RC调用addReference()函数, reference.count =1 ;
2. SP<PERSON> q = p;
接下来,我们有定义了一个新的SP智能指针q, 调用SP类的拷贝构造函数,q的pData同样复制p的pData的值,q的reference拷贝p的reference值
同时,我们发现,q的reference.count加1, 现在 q的reference.count =2;
3. SP<PERSON> r; r = p;
接下来,我们创建一个新的空的智能指针r,并调用assigne operator 赋值函数初始化,同样,由于r != p, 所以原来的r的空间会被释放, 然后将p的空间复制给r。
这个时候r的pData同样指向Person实例的地址,p的reference复制p的reference,并且对reference加1.   现在 r的reference.count =3.

4. 由于 r,q 生命域到达,rq 的析构函数先后被调用。
r首先被析构, 会对reference.count减一,等于2,发现还没到0, 所以不会释放 pdata 和 reference
q其次被析构,会对reference.count减一,等于1,发现还没到0, 所以不会释放 pdata 和 reference

5. p的生命域到达,p调用析构函数
p最后被析构,会对reference.count减一,等于0,发现到0, 所以释放 pdata 和 reference。 此时pdata 就是一开始新创建的Person空间,所以person会被释放,同时Reference也会被释放。

总结:
整个过程中,我们只创建了一次Person实例和Reference实例, 但最多有三个智能指针同时指向他们,通过对实例的被引用次数记录,来“智能”的判断什么时候释放真正的内存空间。

时间: 2024-08-25 20:48:25

【转】 C++易混知识点4: 自己编写一个智能指针(Reference Counting)学习auto_ptr和reference counting的相关文章

【转】C++易混知识点3. New Operator, Operator New, Placement New 实例分析,比较区别

我们知道,C++中引入了New 这个内置符号,很大方便了指针的使用,程序员不必关注与这块堆上新分配的内存是如何来的,如何初始化的,然后如何转换为我们想要的类型指针的.现在,我们重点来分析下这个NEW内置符号背后的步骤和所调用到的函数.这里面涉及到new operator, operator new, placement new. 转载请注明出处: http://blog.csdn.net/elfprincexu 1. New Operator (__type__ * a = new a();)

【转】C++易混知识点5:实例讲解Public Protected Private作用域,继承的区别和用意

大学生涯,涉及到类的作用域,继承都是用的public 共有继承,当时也没想那么多,觉得共有继承多方便,多简单,反正没有太多的限制,不管是类的成员或者是基类的成员函数都可以访问.没有深究.其实这里面真是涉及到了C++面向对象设计当中的封装特性.只暴露那些需要的成员和成员函数,不必过多曝露所有的成员. 转载请注明出处:http://blog.csdn.net/elfprincexu 第一:private.public.protected访问的范围. private:只能由1.该类中的函数:2.其友元

【转】 C++易混知识点2. 函数指针和指针函数的区别

我们时常在C++开发中用到指针,指针的好处是开销很小,可以很方便的用来实现想要的功能,当然,这里也要涉及到指针的一些基本概念.指针不是基本数据类型,我们可以理解他为一种特殊类型的对象,他占据一定空间,但是所带来的好处就是C++如此强大的深层次原因了. 转载请注明出处: http://blog.csdn.net/elfprincexu 1. 指针函数, ( __type__ * func( void, int,) ) 顾名思义,他是一个函数,只不过和一般函数区分的原因是它返回的是一个指针.int*

【干货】 PMP考试易混淆知识点(一)

话说6月的考试刚刚过去,这说明咱们9月的考试即将到临,小编为此给咱们备考9月的一群小伙伴们准备了一打干货,祝愿9月的你学有所得~ PMP考试易混淆知识点(一) 以下内容来自:51CTO  PMP微职位讲师:王安 1.项目管理.项目集.项目组合的区别. 序号 名称 管理 共同特点 实现方法 主要区别 专有特点 应用的时机 1 项目组合管理 是为了实现战略目标而对一个或多个项目组合进行集中管理. 服务与战略目标的实现 对工作优先排序提供所需资源 将相互没有依赖或关系的项目组合在一起 方便实现战略目标

Javascript易错知识点

? JS易错知识点总结: == 和 === 的区别: ==:判断两个变量的值是否相等. ===:判断两个变量的类型和值是否都相等,两个条件同时满足时,表达式为True. switch中break的作用: 如果一个case后面的语句,没有写break,那么程序会向下执行,而不会退出: 例如:当满足条件的case 2下面没有break时,case 3也会执行 1 var num = 2; 2 switch(num){ 3 case 1: 4 alert('case 1'); 5 break; 6 c

C++之易混淆知识点一

1.const.mutable与volatile的区别:const表明内存被初始化以后,程序将不能对它进行修改.volatile则表明,即使程序代码没有对内存单元进行修改,但是里面的值也可能会发生变化.例如:将一个指针指向某个硬件位置,其中包含了来自串行端口的时间和信息,在某些情况下,硬件而不是程序可能会修改其中的内容,或者两个程序可能相互影响,共享数据.该关键字的作用就是为了改善编译器的优化能力.假设编译器发现程序在几条语句中两次使用某个变量的值,则编译器可能不是让程序查找这个编码的值两次,而

JavaScript易错知识点整理

本文是我学习JavaScript过程中收集与整理的一些易错知识点,将分别从变量作用域,类型比较,this指向,函数参数,闭包问题及对象拷贝与赋值这6个方面进行由浅入深的介绍和讲解,其中也涉及了一些ES6的知识点. JavaScript知识点 1.变量作用域 var a = 1; function test() { var a = 2; console.log(a); // 2 } test(); 上方的函数作用域中声明并赋值了a,且在console之上,所以遵循就近原则输出a等于2. var a

jQuery选择器与JavaScript易出错知识点

一.jQuery选择器 基本选择器 1.Id(根据给定的ID匹配一个元素.如果选择器中包含特殊字符,可以用两个斜杠转义.) jQuery代码为$("#myclass") 若有特殊字符则 HTML代码为<span id="myclass[1]" jQuery代码为$("#myclass\\[1\\]") 2.Class(一个用以搜索的类.一个元素可以有多个类,只要有一个符合就能被匹配到) jQuery代码为$(".myclass&q

常问易混淆知识点(嵌入式)

b 一.知识点1 a) 关键字volatile在编译时有什么含义?并给出三个不同使用场景的例子(可以伪代码或者文字描述).b) C语言中static关键字的具体作用有哪些 ?c) 请问下面三种变量声明有何区别?请给出具体含义int const *p;int* const p;int const* const p; a) 用volatile关键字定义变量,相当于告诉编译器,这个变量的值会随时发生变化,每次使用时都需要去内存里 重新读取它的值,并不要随意针对它作优化. 建议使用volatile变量的