由深拷贝与浅拷贝引发的引用计数、写时拷贝技术

原文地址:http://blog.csdn.net/wjxxaut/article/details/52201576

一、理解深拷贝和浅拷贝:

  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class String  
  5. {  
  6. public:  
  7.     String(const char *str = "")  
  8.     {  
  9.         if(str == NULL)  
  10.         {  
  11.             data = new char[1];  
  12.             data[0] = '\0';  
  13.         }  
  14.         else  
  15.         {  
  16.             data = new char[strlen(str)+1];  
  17.             strcpy(data,str);  
  18.         }  
  19.     }   
  20.     ~String()  
  21.     {  
  22.         delete []data;  
  23.         data = NULL;  
  24.     }  
  25. private:  
  26.     char *data;  
  27. };  
  28.   
  29. int main()  
  30. {  
  31.     String s1("hello");  
  32.     String s2 = s1;  
  33.     String s3;  
  34.     s3 = s1;  
  35.     return 0;  
  36. }  


s1给s2初始化时,会调用拷贝构造函数,因为没有编写,则会调用默认的拷贝构造函数,拷贝构造函数会按成员赋值,这样s2的指针会指向s1的指针指向的空间;
但析构的时候,会先释放s2指向的空间,但当析构s1指向的空间时,因为s2和s1是指向相同空间的,s2已经将空间释放,s1就没有空间可以释放,所以s1的析构就导致了程序的非法访问,造成程序的崩溃。这种现象就叫做浅拷贝,即只拷贝指针。
  1. //重写拷贝构造函数:  
  2. String(const String &s)                        //深拷贝  
  3. {  
  4.     data = new char [strlen(s.data)+1];  
  5.     strcpy(data,s.data);  
  6. }  
  7. //重写赋值语句:  
  8. String& operator=(const String &s)             //深赋值         
  9. {  
  10.     if(this != &s)    
  11.     {  
  12.         delete []data;  
  13.         data = new char[strlen(s.data)+1];  
  14.         strcpy(data,s.data)  
  15.     }  
  16.     return *this;  
  17. }  

深拷贝就是在拷贝的时候,将指针指向的空间也一同拷贝,这样,析构的时候,自己释放自己指向的空间就可以了。


二、理解深拷贝和浅拷贝各自的优缺点:


浅拷贝节省空间,相同的数据只保存一份,但因为多个指针指向同一个空间,会引发多次释放的问题;


深拷贝虽然每个指针会指向不同的空间,没有一个空间多次释放的问题,但可能保存的数据都是一样的,这样会导致空间的浪费。


三、使用引用计数解决浅拷贝实现中出现的问题:

所以只要能够解决浅拷贝中的同一个空间多次的释放的问题,当然是最好的!

这就引出了引用计数的方法:

当一个空间被一个指针指向时,计数为1,当每多一个指针指向时,计数加 1.

当析构时,释放一个指针对象,空间不释放,计数减 1,当计数为 0 时,释放空间


  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class String  
  5. {  
  6. public:  
  7.     String(const char *str = "")  
  8.     {  
  9.         if(str == NULL)  
  10.         {  
  11.             data = new char[1];  
  12.             data[0] = '\0';  
  13.         }  
  14.         else  
  15.         {  
  16.             data = new char[strlen(str)+1];  
  17.             strcpy(data,str);  
  18.         }  
  19.         ++use_count;  
  20.     }   
  21.     //重写拷贝构造函数:  
  22.     String(const String &s)                        //浅拷贝,引用计数加 1  
  23.     {  
  24.         data = s.data;  
  25.         ++use_count;  
  26.     }  
  27.     //重写赋值语句:  
  28.     String& operator=(const String &s)             //浅赋值,引用计数加 1         
  29.     {  
  30.         if(this != &s)    
  31.         {  
  32.             data = s.data;  
  33.             ++use_count;  
  34.         }  
  35.         return *this;  
  36.     }  
  37.     ~String()                                      //析构,引用计数减 1  
  38.     {  
  39.         if(--use_count == 0)                   //当引用计数为 0 时,释放空间  
  40.         {  
  41.             delete []data;              
  42.             data = NULL;  
  43.         }  
  44.     }  
  45. private:  
  46.     char *data;  
  47.     static int use_count;            
  48. };  
  49.   
  50. int String::use_count = 0;  
  51.   
  52. int main()  
  53. {  
  54.     String s1("hello");  
  55.     String s2 = s1;  
  56.   
  57.     return 0;  
  58. }  

运行上面的程序看着没有问题,可是,当我们再创建一个不同的对象时发现,不同的空间居然有相同的引用计数


String s3("world");


s3没有拷贝s1和s2,而是一个新的空间的指针对象,但我们发现还是相同的引用计数加 1,所以这样写的引用计数程序是有问题的。


注意:每个空间应该具有自己的引用计数,而不能所有空间共享一个引用计数。


四、解决引用计数中的写时拷贝技术实现


  1. //引用计数器类  
  2. class String_rep  
  3. {  
  4. public:  
  5.     String_rep(const char *str):use_count(0)  
  6.     {  
  7.         if(str == NULL)   
  8.         {  
  9.             data = new char[1];  
  10.             data[0] = '\0';  
  11.         }  
  12.         else  
  13.         {  
  14.             data = new char[strlen(str)+1];  
  15.             strcpy(data,str);  
  16.         }  
  17.     }     
  18.     String_rep(const String_rep &rep):use_count(0)  
  19.     {  
  20.         data = new char[strlen(rep.data)+1];  
  21.         strcpy(data,rep.data);  
  22.     }  
  23.     String_rep& operatro=(const String_rep &rep)  
  24.     {  
  25.         if(this != &rep)  
  26.         {  
  27.             delete []data;  
  28.             data = new char[strlen(rep.data)+1];  
  29.             strcpy(data,rep.data);  
  30.         }  
  31.         return *this;  
  32.     }  
  33.     ~String_rep()  
  34.     {  
  35.         delete []data;  
  36.         data = NULL;  
  37.     }  
  38. public:  
  39.     void increment()  
  40.     {  
  41.         ++use_count;  
  42.     }  
  43.     void decrement()  
  44.     {  
  45.         if(--use_count == 0)  
  46.         {  
  47.             delete this;        //调动自身的析构函数  
  48.         }  
  49.     }  
  50. private:  
  51.     char *data;  
  52.     int use_count;  
  53. };  
  54.   
  55. class String  
  56. {  
  57. public:  
  58.     String(const char *str = "")  
  59.     {  
  60.         rep = new String_rep(str);  
  61.         rep->increment();  
  62.     }  
  63.     String(const String &s)  
  64.     {  
  65.         rep = s.rep;  
  66.         rep->increment();  
  67.     }  
  68.     ~String()  
  69.     {  
  70.           
  71.         rep->decrement();  
  72.     }  
  73. private:  
  74.     Stirng_rep *rep;  
  75. };  
  76.   
  77. int main()  
  78. {  
  79.     String s1("hello");  
  80.     String s2 = s1;  
  81.   
  82.     String s3("world");  
  83.   
  84.     return 0;  
  85. }  

一个String对象中只维护一个 指向String_rep类的rep指针:
s1            String_rep 
[rep] ----- >   data     -------->[ h e l l o \0]
  |            use_count
  |                /
s2              /
[rep]_____/

创建s1对象,调用构造函数,指向一个String_rep对象,引用计数加 1

s1给s2初始化,调用拷贝构造函数,进行浅拷贝,s1和s2指向相同的String_rep对象,引用计数加 1,该对象的指针指向同一个空间

s3           String_rep
[rep]------->   data     -------->[ w o r l d \0]
               use_count

创建s3对象,调用构造函数,指向一个新的String_rep对象,引用计数加 1


赋值语句:

s3 = s2:


  1. String& operator=(const String &s)  //赋值函数的编写要小心,只进行浅拷贝会发生内存泄漏  
  2. {     
  3.     if(this != &s)    
  4.     {  
  5.         rep = s.rep;  
  6.         rep->increment();  
  7.     }     
  8.     return *this;  
  9. }  


赋值函数的编写要小心,只进行浅拷贝会发生内存泄漏,因为s3对象的rep指针原本指向的是String_rep对象,及String_rep
对象指针指向的空间,如果单纯将s2对象的rep值赋值给s3对象的rep值,则s3对象的rep指针指向的空间内存都会泄漏;

重写赋值语句:

  1. String& operator=(const String &s)  
  2. {     
  3.     if(this != &s)    
  4.     {  
  5.         rep->cecrement();   //delete  
  6.         rep = s.rep;        //new  
  7.         rep->increment();   //strcpy  
  8.     }     
  9.     return *this;  
  10. }  


将s3对象rep指针原先指向String_rep的引用计数减 1,再将s3的rep指针赋值为s2的rep指针,该String_rep对象的引用计数 加1


以上的浅拷贝的引用计数方式,解决了相同数据多份空间而造成浪费的问题,但如果我们更改任何一个空间的内容时,所有的拷贝都会发生更改,这是错误的,应该只更改自己的,不应该影响别的对象。
这就提出了写时拷贝技术,即只是拷贝时共享相同的空间,但当自己需要修改数据时,应该将数据拷贝出来,
然后改变自己的指向,即进行深拷贝。


//当需要修改时,在String类中的修改函数


s2.to_upper();

  1. void to_upper()           
  2. {  
  3.     if(rep->use_count > 1)  
  4.     {  
  5.         String_rep *new_rep  = new String_rep(rep->data);  //1.  
  6.         rep->decrement();                                  //2.  
  7.         rep = new_rep;                                     //3.  
  8.         rep->increment();  
  9.     }  
  10.     char *ch = rep->data;                                       //4.  
  11.     while(*ch != '\0')    
  12.     {  
  13.         *ch -= 32;  
  14.         ++ch;  
  15.     }  
  16. }  



当s2对象的rep指针指向的String_rep引用计数大于1时,修改时

1.用原来String_rep对象指针指向的数据创建一个新的String_rep对象;


2.将s2对象的rep指针指向的String_rep引用计数减 1;


3.将s2对象的rep指针指向新的String_rep对象,并将引用计数加 1


4.对s2对象的rep指针指向的新的String_rep对象指针指向的数据进行更改。


当s2对象的rep指针指向的String_rep引用计数等于1时,直接对进行更改

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值