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

  • 相关阅读:
    Windows Azure Storage (17) Azure Storage读取访问地域冗余(Read Access – Geo Redundant Storage, RA-GRS)
    SQL Azure (15) SQL Azure 新的规格
    Azure China (5) 管理Azure China Powershell
    Azure China (4) 管理Azure China Storage Account
    Azure China (3) 使用Visual Studio 2013证书发布Cloud Service至Azure China
    Azure China (2) Azure China管理界面初探
    Azure China (1) Azure公有云落地中国
    SQL Azure (14) 将云端SQL Azure中的数据库备份到本地SQL Server
    [New Portal]Windows Azure Virtual Machine (23) 使用Storage Space,提高Virtual Machine磁盘的IOPS
    Android数据库升级、降级、创建(onCreate() onUpgrade() onDowngrade())的注意点
  • 原文地址:https://www.cnblogs.com/xiongyunqi/p/4389787.html
Copyright © 2020-2023  润新知