深浅拷贝&引用计数写时拷贝

时间:2020-12-21 19:51:58

深浅拷贝
浅拷贝(值拷贝):拷贝构造的时候,直接将原内容的地址交给要拷贝的类,两个类共同指向一片空间。
先来看一段简单的浅拷贝:

class String
{

public:
String(char* str = "")
:_str(new char[strlen(str)+1])
{
strcpy(_str, str);
}
~String()
{
delete[] _str;
}
private:
char*_str;
};
void Test()
{
String s1("hello");
String s2(s1);
}
int main()
{
Test();
return 0;
}

深浅拷贝&引用计数写时拷贝
通过监视窗口可以看到s1,s2同指向一块内存空间
深浅拷贝&引用计数写时拷贝

这使得存在:(缺陷1)s1和S2中包含的指针对象同时指向一块内存,析构时 delete了两次这个内存块出错。(缺陷2)如果对s2进行改变,s1的内容也会改变。
深拷贝:(址拷贝):采取在堆中申请新的空间来存取数据,这样数据之间相互独立。重新开辟一块空间存放s2的内容。
传统写法
深浅拷贝&引用计数写时拷贝

现代写法
深浅拷贝&引用计数写时拷贝

class String
{
public:
String(const char* str = "")
:_str(new char[strlen(str)+1])
{
strcpy(_str, str);
}
//拷贝构造函数 1.传统写法(自己开空间,自己拷贝)
String(const String& s)
:_str(new char[strlen(s._str)+1])
{
strcpy(_str, s._str);
}
String&operator(const String& s)
{
if(this!=&s)
{
delete[] _str;
_str=(new char[strlen(s._str)+1]);
strcpy(_str, s._str);
}
return *this;
//2.现代写法(因为复用了之前的代码,在使用和修改时更加方便)
String(String& s)
:_str(NULL)
{
String tmp(s._str);
swap(_str,tmp._str);
}
String&operator(String& s)
{
swap(_str,s._str);
return *this;
}
~String()
{
delete[] _str;
}
void Print()
{
cout<<s<<endl;
}
private:
char* _str;
};

对于浅拷贝所出现的缺陷1,不仅仅可以使用深拷贝来解决,我们可以通过标记储存字符串那段空间的使用次数,用来真正确定,这块空间是不是真的要释放!这就是引用计数法

引用计数
开辟空间存放一个int* 类型的指针,这个指针指向存储引用计数的空间,多个对象指向同一块的引用计数空间时,说明他们使用的同一字符串!
深浅拷贝&引用计数写时拷贝

class String
{

public:
String(const char *Str = "")
:_Str(new char[strlen(Str) + 1])
, count(new int[1])
{
strcpy(_Str,Str);
*count = 1;
}
String(const String& s)
{
_Str = s._Str;
count = s.count;
(*count)++;
}
~String()
{
if (NULL != _Str && *count == 1)
{
delete[] _Str;
delete[] count;
_Str = NULL;
count = NULL;
}
else if (*count > 1)
{
(*count)--;
}
}
private:
char *_Str;
int *count;
};
void FunTest()
{
String s1("hello");
String s2(s1);
}
int main()
{
FunTest();
system("pause");
return 0;
}

上例中,s2的内容被修改之后,s1的内容也随之修改,这就是引用计数的最大不足!因此,我们又引入写时拷贝。
写时拷贝
在多个对象共用字符串时,有一个对象需要修改字符串的内容,这个时候把字符串拷贝一份赋给这个对象,以免这个对象去修改其他对象所使用的字符串。
深浅拷贝&引用计数写时拷贝
写时拷贝是通过”引用计数”实现的,在分配字节时它多分出4个字节,用来记录有多少变量指向这块空间,也就是类似图中地计数器,当有新的变量指向这块空间时,引用计数加一,当要释放这块空间时,引用计数减一,直到引用计数为0时才真正释放掉这块空间。

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)
{
++GetCount();
}
void Release()
{
if(--(*_rCount)==0)
{
delete[] _str;
delete[] _rCount;
}
}
int& GetCount()
{
return *((int*)_str - 1);
}

void CopyOnWrite()
{
if(*_rCount>1)
{
char* tmp=new char[strlen(_str)+1];
strcpy(tmp,_str);
--(*_rCount);
_str=tmp;
_rCount=new int(1);
}
}

char& operator[](size_t index)
{
CopyOnWrite();
return _str[index];
}
~String()
{
Release();

}
void Display()
{
cout<<_str<<endl;
}
char* c_str()
{
return _str;
}
private:
char* _str;
int* _rCount;
};