由深拷贝与浅拷贝引发的引用计数、写时拷贝技术
来源:互联网 发布: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;}
但析构的时候,会先释放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时,直接对进行更改
- 由深拷贝与浅拷贝引发的引用计数、写时拷贝技术
- 由深拷贝与浅拷贝引发的引用计数、写时拷贝技术
- C++::浅拷贝,深拷贝,引用计数的拷贝,写时拷贝
- String类的浅拷贝、深拷贝、引用计数、写时拷贝
- 浅拷贝、深拷贝与引用计数
- C++写时拷贝技术与引用计数
- 深浅拷贝&引用计数写时拷贝
- 写时拷贝与引用计数
- String类详解(浅拷贝,深拷贝,引用计数,写时拷贝)
- String类---深拷贝,简洁深拷贝,引用计数拷贝,写时拷贝
- 浅析深拷贝之写时拷贝&引用计数
- 引用计数+写时拷贝
- 引用计数写时拷贝
- 引用计数写时拷贝
- 引用计数写时拷贝
- 引用计数+写时拷贝
- 引用计数--写时拷贝
- 引用计数的写时拷贝
- mac iterm综合配置
- 数据结构实验之串二:字符串匹配
- Array类型
- Intellij IDEA artifact 'XXXX:war exploded' has invalid extension
- 引用参数&
- 由深拷贝与浅拷贝引发的引用计数、写时拷贝技术
- C++跑程序所花时间
- POJ 1797 Heavy Transportation
- 数据结构实验之排序二:交换排序 sdut oj 3399
- Category 分类、类别 总结 - iOS
- 数据库的预备知识
- Android 的快捷键总结
- POJ 1129 - Channel Allocation
- 数据结构实验之串三:KMP应用