前面我們探討了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];
}