写时拷贝—C++实现

来源:互联网 发布:mac口红豆沙色是哪款 编辑:程序博客网 时间:2024/06/18 10:59

1.什么是写时拷贝

写时拷贝:引用计数器的浅拷贝,也称延时拷贝。

实现原理:写时拷贝是通过“引用计数”实现的,在分配空间的时候多分配出4个字节,用来记录有多少个指针指向这块空间,当有新的指针指向这块空间时,引用计数加1,当要释放这块空间时,引用计数减1,直到引用计数减为0时才真正释放掉这块空间。当有的指针要改变这块空间的值时,再为这个指针分配自己的空间(注意这时的引用计数变化,旧的空间引用计数减1,新分配的空间引用计数加1)。

2.通过写时拷贝实现简单的String类

(一)独立开辟空间进行引用计数

1.动态开辟两个空间,一个用来存放字符串,一个用来计数

2.每次拷贝构造时直接把字符串指针赋给新的String,count++

3.释放String时,count--,然后检查count是否为0,若为0就释放掉str和refCount


下面我们通过写时拷贝(一)实现String类

class String{public:String(char* str="\0"):_str(new char[strlen(str)+1]),_refCount(new int(1)){strcpy(_str, str);}String(String& s):_str(s._str), _refCount(s._refCount){(*_refCount)++;}void Delete(){if (--(*_refCount) == 0){cout << "~String" << endl;delete[] _str;delete _refCount;}}~String(){Delete();}String &operator=(String &s){if (_str != s._str){Delete();_str = s._str;_refCount = s._refCount;(*_refCount)++;}return *this;}private:char* _str;int* _refCount;};
我们通过调试观察下:


我们可以这样思考:如果一个对象第一次开辟空间存放字符串再开辟一块新的空间存放新的引用计数,当它拷贝构造其它对象时让其它对象的引用计数都指向存放引用计数的同一块空间,refCount设置成int*就可以啦,但是这种方式有缺陷。

缺陷一:每次new两块空间,创建多个对象的时候效率比较低 
缺陷二:它多次分配小块空间,容易造成内存碎片化,导致分配不出来大块内存

所以我们可以对其进行优化,采取第二种方式

(二)同一块空间上进行引用计数

开辟一个空间,前 4个字节用来计数,剩下的用来存放字符串


利用s1拷贝构造s2时,s1,s2指向同一内存块,refcount为指向该内存块的对象数目。

用法步骤同方法(一)。

下面我们通过写时拷贝(二)实现String类

class String{public:String(char* str = ""):_str(new char[strlen(str) + 1 + 4]){*(int*)_str = 1;_str += 4;strcpy(_str, str);}String(const String& s):_str(s._str){++refCount();}~String(){Delete();}String& operator=(const String& s){if (this != &s){Delete();_str = s._str;refCount()++;}return *this;}void Delete(){if (--refCount() == 0){_str -= 4;delete[] _str;}}int& refCount(){return *((int*)_str - 1);}

注意:1).如果引用计数大于1,在写之前必须拷贝这块内存单元,这样就不会影响其他对象使用这块内存了。(因为可能不止一个对象会使用这块内存,修改了自己等于修改了别人,所以再向这块内存写之前必须要确保没有其他对象使用它)。

2).由于计数器存放在了_str首地址-4的地址上,所以在析构时一定要注意全部释放,避免内存泄漏。



原创粉丝点击