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

来源:互联网 发布:java工程师岗位总结 编辑:程序博客网 时间:2024/05/29 17:22

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

#include <iostream>using namespace std;class String{public:String(const char *str = ""){if(str == NULL){data = new char[1];data[0] = '\0';}else{data = new char[strlen(str)+1];strcpy(data,str);}} ~String(){delete []data;data = NULL;}private:char *data;};int main(){String s1("hello");String s2 = s1;String s3;s3 = s1;return 0;}


s1给s2初始化时,会调用拷贝构造函数,因为没有编写,则会调用默认的拷贝构造函数,拷贝构造函数会按成员赋值,这样s2的指针会指向s1的指针指向的空间;
但析构的时候,会先释放s2指向的空间,但当析构s1指向的空间时,因为s2和s1是指向相同空间的,s2已经将空间释放,s1就没有空间可以释放,所以s1的析构就导致了程序的非法访问,造成程序的崩溃。这种现象就叫做浅拷贝,即只拷贝指针。

//重写拷贝构造函数:String(const String &s)                        //深拷贝{data = new char [strlen(s.data)+1];strcpy(data,s.data);}//重写赋值语句:String& operator=(const String &s)             //深赋值       {if(this != &s){delete []data;data = new char[strlen(s.data)+1];strcpy(data,s.data)}return *this;}

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


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


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


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


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

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

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

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

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


#include <iostream>using namespace std;class String{public:String(const char *str = ""){if(str == NULL){data = new char[1];data[0] = '\0';}else{data = new char[strlen(str)+1];strcpy(data,str);}++use_count;} //重写拷贝构造函数:String(const String &s)                        //浅拷贝,引用计数加 1{data = s.data;++use_count;}//重写赋值语句:String& operator=(const String &s)             //浅赋值,引用计数加 1       {if(this != &s){data = s.data;++use_count;}return *this;}~String()                                      //析构,引用计数减 1{if(--use_count == 0)                   //当引用计数为 0 时,释放空间{delete []data;            data = NULL;}}private:char *data;static int use_count;          };int String::use_count = 0;int main(){String s1("hello");String s2 = s1;return 0;}

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


String s3("world");


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


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


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


//引用计数器类class String_rep{public:String_rep(const char *str):use_count(0){if(str == NULL){data = new char[1];data[0] = '\0';}else{data = new char[strlen(str)+1];strcpy(data,str);}}String_rep(const String_rep &rep):use_count(0){data = new char[strlen(rep.data)+1];strcpy(data,rep.data);}String_rep& operatro=(const String_rep &rep){if(this != &rep){delete []data;data = new char[strlen(rep.data)+1];strcpy(data,rep.data);}return *this;}~String_rep(){delete []data;data = NULL;}public:void increment(){++use_count;}void decrement(){if(--use_count == 0){delete this;        //调动自身的析构函数}}private:char *data;int use_count;};class String{public:String(const char *str = ""){rep = new String_rep(str);rep->increment();}String(const String &s){rep = s.rep;rep->increment();}~String(){rep->decrement();}private:Stirng_rep *rep;};int main(){String s1("hello");String s2 = s1;String s3("world");return 0;}

一个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:


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


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

重写赋值语句:

String& operator=(const String &s){if(this != &s){rep->cecrement();   //deleterep = s.rep;        //newrep->increment();   //strcpy}return *this;}


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


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


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


s2.to_upper();

void to_upper()         {if(rep->use_count > 1){String_rep *new_rep  = new String_rep(rep->data);  //1.rep->decrement();                                  //2.rep = new_rep;                                     //3.rep->increment();}char *ch = rep->data;                                       //4.while(*ch != '\0'){*ch -= 32;++ch;}}



当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时,直接对进行更改

1 0
原创粉丝点击