前面我们探讨了String类中深浅拷贝问题,相比于浅拷贝来说,深拷贝的效率较低,在深拷贝中,每拷贝一个对象就需要开辟空间和释放空间,赋值运算符重载也一样需要重新开辟和释放空间。可是当拷贝和赋值的对象只用于"读",而不是用于"写",我们就可以不用重新开辟空间。由此,有了引用计数的浅拷贝。
我们需要使用一个变量可以标记同一块空间同时这块空间有多个对象在使用,当析构时,先判断这个标记的变量等不等于1,当等于1时,说明此时只有一个对象在使用这段空间,则在使用完毕后进行析构,否则则让这个变量减1 。
需要说明的是这个计数变量需要用指针来进行管理。使得拷贝的对象共同指向一个引用计数。
赋值运算符重载函数中的两种情况:
下面我们来看代码实现:
//String_ref.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class String
{
public:
String(const char* data = "");
String(const String& s);
~String();
String& operator=(String& s);
private:
int& GetRef(char* data);
void Release();
char* _data;
int* _RefCount;
};
#include"string_ref.h"
使用指针,拷贝对象共同指向引用计数
String::String(const char* data)
{
if (NULL== data)
{
_data = new char[1];
*_data = '\0';
}
else
{
_data = new char[strlen(data) + 1];
strcpy(_data, data);
}
_RefCount = new int[1];
*_RefCount = 1;
}
String::String(const String& s)
:_data(s._data)
, _RefCount(s._RefCount)
{
strcpy(_data, s._data);
(*_RefCount)++;
}
String& String::operator=(String& s)
{
if (this != &s)
{
if ((--*_RefCount) == 0)
{
delete[] _data;
_data = NULL;
delete[] _RefCount;
_RefCount = NULL;
}
_data = s._data;
_RefCount = s._RefCount;
(*s._RefCount)++;
}
return *this;
}
String::~String()
{
if (--*_RefCount == 0 && NULL != _data)
{
delete[] _data;
_data = NULL;
delete[] _RefCount;
_RefCount = NULL;
}
}
void Test1()
{
String s1("hello");
String s2(s1);
String s3(s2);
String s4("world");
s4 = s2;
}
int main()
{
Test1();
}
下面我们来看调试结果:
如上可知,使用指针可完成引用计数的浅拷贝,四个对象指向同一块空间。
但每构造出一个对象,都需要开辟两块空间,这样很容易造成内存碎片。由new[]可以想到类似的模型,只开辟一块内存空间,把引用计数放在字符串首地址的前四个字节上,这样不但解决了内存碎片问题,而且也可以提高程序运行的效率。
下面我们来看代码实现:
//String_ref.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class String
{
public:
String(const char* data = "");
String(const String& s);
~String();
String& operator=(String& s);
private:
int& GetRef(char* data);
void Release();
char* _data;
/*int* _RefCount;*/
};
#include"string_ref.h"
String::String(const char* data)
{
if (NULL == data)
{
_data = new char[5];
_data += 4;
*_data = '\0';
}
else
{
_data = new char[strlen(data) + 5];
_data += 4;
strcpy(_data, data);
}
GetRef(_data) = 1;
}
void String::Release()
{
if (--GetRef(_data)==0)
{
_data -= 4;
delete[] _data;
}
}
String::~String()
{
Release();
}
int& String::GetRef(char* data)
{
return *(int*)(_data-4);
}
String::String(const String& s)
:_data(s._data)
{
GetRef(_data)++;
}
String& String::operator=(String& s)
{
if (_data != s._data)
{
Release();
_data = s._data;
GetRef(_data)++;
}
return *this;
}
void Test()
{
String s1("hello");
String s2(s1);
String s3(s2);
String s4("world");
s4 = s2;
}
int main()
{
Test();
return 0;
}
我们来看运行结果:
但引用计数的浅拷贝仍然存在缺陷,当几个共用同一块空间的对象修改字符串中的值,则会导致所有共用这块空间的对象中的内容被破坏掉。由此引入了写时拷贝,简单点来说,就是写的时候进行拷贝(深拷贝)。下面我们来看代码实现。
String.h写时拷贝
#pragma once
#include<iostream>
using namespace std;
#include<assert.h>
class String
{
public:
String(const char* str = "")//构造函数
{
if (NULL == str)
{
char* pTemp = new char[1 + 4];
_pStr = pTemp + 4;
_GetRefer() = 1;
*_pStr = '\0';
}
else
{
char* pTemp = new char[strlen(str) + 1 + 4];
_pStr = pTemp + 4;
strcpy(_pStr, str);
_GetRefer() = 1;
}
}
String(const String& s)
:_pStr(s._pStr)
{
_GetRefer()++;
}
~String()
{
Release();
}
String& operator=(const String& s);
char& operator[](size_t index);
const char& operator[](size_t index)const;
int& String::_GetRefer();
void Release();
friend ostream& operator<<(ostream& _cout, const String& s);
private:
char* _pStr;
};
//String.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"String.h"
int& String::_GetRefer()//获取引用计数
{
return *((int*)_pStr - 1);
}
void String::Release()//释放空间
{
if (--_GetRefer() == 0)
{
_pStr -= 4;
delete[] _pStr;
_pStr = NULL;
}
}
String& String::operator=(const String& s)
{
if (_pStr != s._pStr)
{
if (--_GetRefer() == 0)//需要考虑两种情况
{
Release();
_pStr = s._pStr;
_GetRefer()++;
}
else
{
_pStr = s._pStr;
_GetRefer()++;
}
}
return *this;
}
char& String::operator[](size_t index)
{
if (_GetRefer() > 1)
{
_GetRefer()--;
char* pTemp = new char[strlen(_pStr) + 1 + 4];
pTemp += 4;
strcpy(pTemp, _pStr);
_pStr = pTemp;
_GetRefer() = 1;//新开的空间
}
return _pStr[index];
}
const char& String::operator[](size_t index)const
{
return _pStr[index];
}
void Test1()
{
const String s1("hello");
String s2(s1);
//String s3;
//s3 = s2;
cout << s1[4] << endl;
}
int main()
{
Test1();
return 0;
}
[]重载运算符需要成对重载:
char& String::operator[](size_t index)
{
if (_GetRefer() > 1)
{
char* pTemp = new char[strlen(_pStr) + 1 + 4];
pTemp += 4;
strcpy(pTemp, _pStr);
_GetRefer()--;
_pStr = pTemp;
_GetRefer() = 1;//新开的空间
}
return _pStr[index];
}
const char& String::operator[](size_t index)const
{
return _pStr[index];
}