引用计数写时拷贝
来源:互联网 发布:安卓版ps软件中文版 编辑:程序博客网 时间:2024/05/19 23:00
浅拷贝是值拷贝,由之前深拷贝分析的文章知,浅拷贝后,两个指针指向同一空间。容易出现多次释放同一空间的问题,以及修改其中一个指针指向的内容影响其他指针指向的内容的问题。而深拷贝效率低,每次对对象进行值和空间同时拷贝,但这样会存开辟很多空间。为了避免产生更多的空间,引入写时拷贝,当对空间进行更改时,检查是否有除了自己以外的对象使用该块空间,若有,则自己重新开辟空间进行改正,不影响其他对象;若没有其他对象使用此空间,则说明只有自己使用此空间,直接进行更改。此时引用计数用来统计有多少对象使用此空间,克服了浅拷贝和深拷贝存在的问题。
void TestString(){ String s1; for(size_t i=0;i<1000000;i++) { String s2(s1);//此处的拷贝构造为深拷贝时,程序会崩溃 }}
一、分析写时拷贝。
1、引用计数的工作方式
1)除了初始化对象外,每个构造函数(拷贝构造函数除外)要建立一个引用计数,用来记录多少对象与正在创建的对象为共享一块内存空间。当我们创建一个对象时,只有一个对象用该空间,故将引用计数初始化为1。
2)拷贝构造函数不分配新的计数器,而是拷贝数据成员,包括计数器。拷贝构造递增计数器,指出给定的对象的内存空间被一个新的用户共用。
3)析构函数递减计数器,指出共用空间的用户少了一个,如果计数器为0,则释放该空间。
4)赋值运算符重载,递增右侧运算对象的计数器,递减左侧运算对象的计数器。如果左侧运算对象的计数器为0,就要对该对象进行销毁。
2、写时拷贝
写时拷贝在写的时候(即改变对象内容时)才会真正的开辟空间拷贝(深拷贝),如果只是对数据进行读,只会对数据进行浅拷贝。
写时拷贝:引用计数器的浅拷贝,又称延时拷贝,写时拷贝技术是通过“引用计数”实现的,在分配空间的时候多分配4或8个字节,用来记录有多少指针指向该空间,当有新指针指向这块空间时,引用计数加1。当要释放该块空间时,引用计数减1,直到引用计数减为0才释放掉该块空间。当有的指针要改变这块空间的值时,再为这个指针分配自己的空间(此时,引用计数的变化,旧空间的引用计数减1,新分配的空间的引用计数加1)。
二、完成写时拷贝的各种方案。
1、分析以下String的copy on write的几种实现方案哪种可以?为什么?
//方案1class string{private: char* _str; int _refcount;}//方案1实现class String{ public: String(char* str=" ") :_refCount(0) { _str=new char[strlen(str)+1]; strcpy(_str,str); _refCount++; } String(String &s) :_refCount(s._refCount) { _str=s._str; _refCount++; s._refCount=_refCount; //这里虽然可以让两个对象的_refCount相等,但 //如果超出两个对象的_str指针都指向同一块内存时, //就无法让所有对象的_refCount都保持一致 } ~String() { if(--_refCount==0) { delete[] _str; _str=NULL; } } private: char* _str; int _refCount; };
解析:该方法中,将用作计数的整形变量_refcount定义为类的私有成员变量,任何一个对象都有它自己的成员变量 _refcount,它们互不影响,难以维护。只要拷贝出了对象, _refcount大于了0,每个对象在调用自己的析构函数时 _refcount不等于0,那么它们指向的那块内存都将得不到释放,无法达到想要的效果。
//方案2class String{public: String(char* str=" ") { _str=new char[strlen(str)+1]; strcpy(_str,str); Count++; } String(String &s) { _str=s._str; Count++; } ~String() { if(--Count==0) { delete[] _str; _str=NULL; } }private: char* _str; static int count;};int String::count = 0; //初始化count
解析:该方案设置了一个静态整形变量来计算指向一块内存的指针的数量,每析构一次count减1,直到它等于0(也就是没有指针再指向它的时候)再去释放这块内存空间。但是该方案只适用于只调用一次构造函数,只有一块内存的情形,如果多次调用构造函数构造对象,新构造的对象照样会改变count的值,那么最后只释放了第一块内存,后来的内存无法释放会造成内存泄漏。
//方案3class String{ private: char* _str; int* _refCount;};
问题的关键是要为每一块内存设置一个引用计数,而不是为每一个对象建立一个引用计数,当指向这块内存的指针数为0时,再去释放它。
本方案设置了一个int型的指针变量用来引用计数,每份内存空间对应一个引用计数,而不是每个对象对应一个引用计数,而且不同内存之间的引用计数互不影响,不会出现方案一和方案二出现的问题。
2、 string中的两种写时拷贝
1)方法一:
当创建一个对象S1,引用计数为1,再拷贝构造一个对象S2时,引用计数自动加1,此时为2。如果S2要对自身进行修改,检查引用计数_refCount是否大于1,如果是大于1,则S2重新开偏僻一块空间,修改S1原来的引用计数 _refCount,使其减1。而重新开辟的空间中此时只有S2一个对象,所以引用计数 _refCount为1。
总结:此方案的写时拷贝计数是同时开辟两块空间,一个存放自身容量,一个存放引用计数 _refCount,同时管理两块空间,统计当前使用此空间的对象数,当要修改当前空间的时候,进行引用计数判断,决定是否要开辟空间。
class String{public: String(const char* str=" ") :_str(new char[strlen(str)+1]) ,_refCount(new int(1)) { strcpy(_str,str); } //S2(S1) String(String &s) { _str=s._str; _refCount=s._refCount; ++(*_refCount); } void Release() { if(--(*_refCount)==0) { delete[] _str; delete _refCount; } } //S4=S1 String& operator= (const String& s) { if(_str!=s._str) { Release(); _refCount=s._refCount; _str=s._str; (*_refCount)++; } return *this; } //copy on write void CopyOnWrite() { if(*_refCount>1) { char* tmp=new char[strlen(_str)+1]; strcpy(tmp,_str); --(*_refCount); _str=tmp; _refCount=new int(1); } } //如果指向这块空间的指针只有这一个,直接进行修改; //如果指向这块空间的指针数大于1,则调用写时拷贝进行开空间拷贝, //1)减引用计数2)拷贝 3)创建新的引用计数 char* operator [] (size_t index) { if(*_refCount>1) { CopyOnWrite(); } return _str[index]; } ~String() { Release(); } char* C_str() { return *_str; }private: char* _str; int* _refCount;};
2)方法二:
//方法二class String{ private: char* _str;};
上一种方法是开辟了两块空间进行管理,本方法采用开辟一块空间进行写时拷贝。把用来计数的整形指针变量放在所开辟的内存空间的头4或8个字节。用 * ((int * ) _str)就能取得计数值。真正存放内容的空间从第5或9个字节开始。一次性多开辟4或8个字节进行写时拷贝。
当进行操作时,先检查引用计数个数,然后进行判断是否开辟空间,同时修改引用计数的值,放置空间不能释放。
当创建了3个对象,S1,S2,S3同时指向一个空间,此时引用计数为3,再创建一个对象S4,S4 的引用计数为1。
再进行S3=S4;此时对应的引用计数和对象的指向都需要改动,如下图:
对象S3指向了S4,同时S3原来的空间的引用计数进行减1,新指向空间的引用计数进行加1。
总结:方案2的写时拷贝计数使用一块空间进行内容和引用计数的管理和操作,不用开辟两块空间,方便管理。
class String{public: String(const char* str=" ") :_str(new char[strlen(str)+5]) { *((int*)_str)=1; _str+=4; strcpy(_str,str); } int& GetRefCount() { return *((int*)(_str-4)); } //S2(S1) String(String &s) :_str(s._str) { GetRefCount()++; } void Release() { if(--GetRefCount()==0) { delete[] (_str-4); } } //S4=S1 String& operator= (const String& s) { if(_str!=s._str) { Release(); _str=s._str; GetRefCount()++; } return *this; } //copy on write void CopyOnWrite() { if(GetRefCount()>1) { GetRefCount()--; char* tmp=new char[strlen(_str)+4]; tmp+=4; strcpy(tmp,_str); _str=tmp; GetRefCount()=1; } } char* operator [] (size_t index) { if(GetRefCount()>1) { CopyOnWrite(); } return _str[index]; } ~String() { Release(); } char* C_str() { return *_str; }private: char* _str;};
http://www.cnblogs.com/Lynn-Zhang/archive/2016/04/17/5400714.html
http://blog.csdn.net/zone_programming/article/details/48372941
- 引用计数+写时拷贝
- 引用计数写时拷贝
- 引用计数写时拷贝
- 引用计数写时拷贝
- 引用计数+写时拷贝
- 引用计数--写时拷贝
- 深浅拷贝&引用计数写时拷贝
- C++写时拷贝,引用计数
- C++ 引用计数写时拷贝
- 引用计数的写时拷贝
- 引用计数的写时拷贝
- String--引用计数写时拷贝
- String-引用计数的写时拷贝
- 写时拷贝与引用计数
- 引用计数的写时拷贝
- string引用计数的写时拷贝
- 【c++】深浅拷贝,引用计数写时拷贝
- 浅析深拷贝之写时拷贝&引用计数
- 【Prufer编码+组合】BZOJ1005(HNOI2008)[明明的烦恼]题解
- Java8 Lamdba 在Android Studio 使用
- muduo网络库学习(九)日志类Logger和LogStream,将日志信息打印到屏幕
- Python数据迭代
- 基于DR模式的LVS
- 引用计数写时拷贝
- 关于Visual Studio "当前不会命中断点.还没有为该文档加载任何符号"的解决方法
- 【Maven】settings配置了解
- zookeeper javaApi/zkclient
- CNCC day2下午 人工智能与机器学习前沿技术论坛
- 欢迎使用CSDN-markdown编辑器
- C++实现顺序表和单链表
- caffe中卷积层的权重初始化
- 【perl】学习笔记(五)--文件读写