zoukankan      html  css  js  c++  java
  • 【转】 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.   
    10.         void Display()  
    11.         {  
    12.             printf("Name = %s Age = %d  ", pName, age);  
    13.         }  
    14.         void Shout()  
    15.         {  
    16.             printf("Ooooooooooooooooo",);  
    17.         }   
    18. };  
    现在,我们开始使用这个类
    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.   
    12.     T& operator* ()  
    13.     {  
    14.         return *pData;  
    15.     }  
    16.   
    17.     T* operator-> ()  
    18.     {  
    19.         return pData;  
    20.     }  
    21. };  
    22.   
    23. void main()  
    24. {  
    25.     SP<PERSON> p(new Person("Scott", 25));  
    26.     p->Display();  
    27.     // Dont need to delete Person pointer..  
    28. }  
    通过泛型编程,我们可以使用任何类型的指针,但是上面还不是完美,考虑一下下面的案例
    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.   
    6.     public:  
    7.     void AddRef()  
    8.     {  
    9.         // Increment the reference count  
    10.         count++;  
    11.     }  
    12.   
    13.     int Release()  
    14.     {  
    15.         // Decrement the reference count and  
    16.         // return the reference count.  
    17.         return --count;  
    18.     }  
    19. };  
    现在我们有了一个RC类,这个类只做被引用的次数,唯一的数据成员就是用来跟踪被引用的次数。
    结合我们刚才的SP类,我们稍作改动
    1. template < typename T > class SP  
    2. {  
    3. private:  
    4.     T*    pData;       // pointer  
    5.     RC* reference;     // Reference count  
    6.   
    7. public:  
    8.     SP() : pData(0), reference(0)   
    9.     {  
    10.         // Create a new reference   
    11.         reference = new RC();  
    12.         // Increment the reference count  
    13.         reference->AddRef();  
    14.     }  
    15.   
    16.     SP(T* pValue) : pData(pValue), reference(0)  
    17.     {  
    18.         // Create a new reference   
    19.         reference = new RC();  
    20.         // Increment the reference count  
    21.         reference->AddRef();  
    22.     }  
    23.   
    24.     SP(const SP<T>& sp) : pData(sp.pData), reference(sp.reference)  
    25.     {  
    26.         // Copy constructor  
    27.         // Copy the data and reference pointer  
    28.         // and increment the reference count  
    29.         reference->AddRef();  
    30.     }  
    31.   
    32.     ~SP()  
    33.     {  
    34.         // Destructor  
    35.         // Decrement the reference count  
    36.         // if reference become zero delete the data  
    37.         if(reference->Release() == 0)  
    38.         {  
    39.             delete pData;  
    40.             delete reference;  
    41.         }  
    42.     }  
    43.   
    44.     T& operator* ()  
    45.     {  
    46.         return *pData;  
    47.     }  
    48.   
    49.     T* operator-> ()  
    50.     {  
    51.         return pData;  
    52.     }  
    53.       
    54.     SP<T>& operator = (const SP<T>& sp)  
    55.     {  
    56.         // Assignment operator  
    57.         if (this != &sp) // Avoid self assignment  
    58.         {  
    59.             // Decrement the old reference count  
    60.             // if reference become zero delete the old data  
    61.             if(reference->Release() == 0)  
    62.             {  
    63.                 delete pData;  
    64.                 delete reference;  
    65.             }  
    66.   
    67.             // Copy the data and reference pointer  
    68.             // and increment the reference count  
    69.             pData = sp.pData;  
    70.             reference = sp.reference;  
    71.             reference->AddRef();  
    72.         }  
    73.         return *this;  
    74.     }  
    75. };  
    接下来我们看下客户端调用情况
    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.         SP<PERSON> r;  
    11.         r = p;  
    12.         r->Display();  
    13.         // Destructor of r will be called here..  
    14.     }  
    15.     p->Display();  
    16.     // Destructor of p will be called here   
    17.     // and person pointer will be deleted  
    18. }  

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

    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实例, 但最多有三个智能指针同时指向他们,通过对实例的被引用次数记录,来“智能”的判断什么时候释放真正的内存空间。

  • 相关阅读:
    (4.3)基于机器学习(分类)的酒店评论倾向性分析
    (4.2)基于LingPipe的文本基本极性分析【demo】
    (4.1)LingPipe在Eclipse中的运行
    微信获取openid
    微信token
    js跳转整理(简记)
    阅读有感
    normalize.css v2.1.2 翻译
    来,让我们谈一谈 Normalize.css
    jquery ajax事件执行顺序
  • 原文地址:https://www.cnblogs.com/xiongyunqi/p/4389787.html
Copyright © 2011-2022 走看看