版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
引用计数
深拷贝
多个对象共享同一份资源时,最后能够保证该资源只被释放一次
应该由哪个对象释放资源?
由最后一个使用该资源的对象去释放
怎么知道一个对象是最后一个使用该资源的对象?
给一个计数,记录使用该资源对象的个数
实现
计数用普通整型
先来看一个例子
class string
{
public:
string(char *str = "")
{
//如果指针为空,则初始化位空字符串
if (nullptr == str)
str = "";
//申请空间
_str = new char[strlen(str) + 1];
//初始化一个对象占一个资源,引用计数+1
_count = 1;
//拷贝数据
strcpy(_str, str);
}
string( string& s)
:_str(s._str)
, _count(++s._count)
{
}
string& operator=(const string& s)
{
//自己给自己赋值,不用做任何操作
if (this != &s)
{
}
return *this;
}
~string()
{
//每次释放对象计数都要减一,减完之后要看_count是不是0
if (_str && 0 == --_count)
delete[]_str;
_str = nullptr;
}
//编译器生成的默认赋值运算符重载存在浅拷贝,而且会有资源泄露,没有释放资源
private:
char * _str;
int _count;
};
在类中增加一个变量记录使用资源的对象数
在类中增加int类型的成员变量----不行,因为这种变量每个对象都存在一份
普通的成员变量,每个对象都有一份,一个对象在修改计数时,不会影响其他对象
导致:资源没有释放而引起内存泄露
将计数变为静态成员变量
class string
{
public:
string(char *str = "")
{
//如果指针为空,则初始化位空字符串
if (nullptr == str)
str = "";
//申请空间
_str = new char[strlen(str) + 1];
//初始化一个对象占一个资源,引用计数+1
_count = 1;
//拷贝数据
strcpy(_str, str);
}
string(string& s) //静态成员变量不能再在初始化列表中使用
:_str(s._str)
{
++_count;
}
string& operator=(const string& s)
{
//自己给自己赋值,不用做任何操作
if (this != &s)
{
}
return *this;
}
~string()
{
//每次释放对象计数都要减一,减完之后要看_count是不是0
if (_str && 0 == --_count)
delete[]_str;
_str = nullptr;
}
//编译器生成的默认赋值运算符重载存在浅拷贝,而且会有资源泄露,没有释放资源
private:
char * _str;
static int _count;
};
int string::_count = 0;
将计数给成静态类型成员变量----不行
静态类型成员是所有对象共享,计数应该与资源个数保持一致,有多少资源就要要多少计数
计数为整型指针类型
一个对象修改,另外一个对象也能看见
class string
{
public:
string(char *str = "")
:_pCount(new int (1))
{
//如果指针为空,则初始化位空字符串
if (nullptr == str)
str = "";
//申请空间
_str = new char[strlen(str) + 1];
//拷贝数据
strcpy(_str, str);
}
string(string& s) //静态成员变量不能再在初始化列表中使用
:_str(s._str) //两个对象共用同一份资源
, _pCount(s._pCount) //两个对象共用一个计数
{
++(*_pCount);
}
//s2 = s1
//s2原来的资源将不再使用---应该给原来的计数-1
// 计数非0:
// 计数为0: 释放掉原来的资源
//s2应该与s1共享同一份资源:计数++
string& operator=(const string& s)
{
//自己给自己赋值,不用做任何操作
if (this != &s)
{
//让当前对象与其管理的资源分离开
if (0 == --*_pCount)
{
delete[]_str;
delete _pCount;
}
//与s共享资源
_str = s._str;
_pCount = s._pCount;
++ (*_pCount);
}
return *this;
}
~string()
{
//每次释放对象计数都要减一,减完之后要看_count是不是0
if (_str && 0 == -- *_pCount)
{
delete[]_str;
_str = nullptr;
delete _pCount;
_pCount = nullptr;
}
}
private:
char * _str;
int* _pCount;
};
引用计数也有缺陷
如果出现这种情况
void TestString()
{
bite::string s1("hello");
bite::string s2(s1);
bite::string s3("world");
bite::string s4(s3);
s3 = s1; //s3不需要释放原来的资源,因为还有s4在用
s1 = s4; //s4是最后使用资源的对象,所以需要释放
}
这种情况程序走到末尾,4个对象共用同一块空间,如果用[]
运算符去修改对象s1的值,那么其他对象也都被修改
写时拷贝
所有对象共享一份资源时,读数据不用拷贝,一但有对象要修改,则单独为该对象拷贝一份资源
所以当出现所有写操作或者可能会引起写操作的方法,都会把当前对象修改掉,所以要分离对象’
namespace bite
{
class string
{
public:
string(char *str = "")
:_pCount(new int (1))
{
//如果指针为空,则初始化位空字符串
if (nullptr == str)
str = "";
//申请空间
_str = new char[strlen(str) + 1];
//拷贝数据
strcpy(_str, str);
}
string(string& s) //静态成员变量不能再在初始化列表中使用
:_str(s._str) //两个对象共用同一份资源
, _pCount(s._pCount) //两个对象共用一个计数
{
++(*_pCount);
}
//s2 = s1
//s2原来的资源将不再使用---应该给原来的计数-1
// 计数非0:
// 计数为0: 释放掉原来的资源
//s2应该与s1共享同一份资源:计数++
string& operator=(const string& s)
{
//自己给自己赋值,不用做任何操作
if (this != &s)
{
//让当前对象与其管理的资源分离开
if (0 == --*_pCount)
{
delete[]_str;
delete _pCount;
}
//与s共享资源
_str = s._str;
_pCount = s._pCount;
++ (*_pCount);
}
return *this;
}
char& operator[](size_t index)
{
//该操作可能会改变当前对象的内容
//必须:分离当前对象
if (GetRef() > 1)
{
string strtemp(_str);//构造临时对象
this->swap(strtemp); //当前对象与临时对象交换
}
return _str[index];
}
~string()
{
//每次释放对象计数都要减一,减完之后要看_count是不是0
if (_str && 0 == -- *_pCount)
{
delete[]_str;
_str = nullptr;
delete _pCount;
_pCount = nullptr;
}
}
void swap(string &s)
{
std::swap(_str, s._str);
std::swap(_pCount, s._pCount);
}
private:
//获取引用计数
int GetRef()
{
return *_pCount;
}
private:
char * _str;
int* _pCount;
};
}
void TestString()
{
bite::string s1("hello");
bite::string s2(s1);
bite::string s3("world");
bite::string s4(s3);
s3 = s1; //s3不需要释放原来的资源,因为还有s4在用
s1 = s4; //s4是最后使用资源的对象,所以需要释放
s1[0] = 'H';
char& rc = s1[0];
rc = 'H';
}
写时拷贝单线程底下没有问题,但在多线程下可能会出错
~string()
{
//每次释放对象计数都要减一,减完之后要看_count是不是0
if (_str && 0 == -- *_pCount)
{
delete[]_str;
_str = nullptr;
delete _pCount;
_pCount = nullptr;
}
}
线程1计数减过了但是时间片到了,还没来的及与0比较。线程2过来,发现资源还存在,而且线程2时间片充足,就会去释放资源。释放完后,线程1又开始执行,发现计数已经变为0,就会把资源再释放一次,也会造成代码崩溃
模拟实现string
namespace bite
{
class string
{
public:
typedef char* iterator;
public:
string(const char* str = "")
{
if (str == nullptr)
str = "";
//当前对象开辟空间
_size = strlen(str);
_capacity = _size ;
_str = new char[_capacity + 1];
//拷贝元素
strcpy(_str, str);
}
//放入n个字符ch
string(size_t n, char ch)
:_size(n)
, _capacity(n)
, _str(new char[n + 1])
//此处不能new char[_capacity],因为成员变量初始化,只跟声明顺序有关,_str先于_capacity声明,所以
//先初始化
{
memset(_str, ch, n);
_str[n] = '\0'; //最后一个位置设置为\0
}
//[begin,end)
string(char* begin, char* end)
{
_size = end - begin;
_capacity = _size;
_str = new char[_size + 1];
strncpy(_str, begin, _size);
_str[_size] = '\0';
}
string(const string& s)
:_size(s._size)
, _capacity(s._size)
{
_str = new char[_capacity + 1];
strcpy(_str, s._str);
}
string& operator=(const string& s)
{
if (this != &s)
{
int len = strlen(s._str) ;
char * p = new char[len + 1];
strcpy(p, s._str);
delete[]_str;
_str = p;
_size = len;
_capacity = len;
}
return *this;
}
~string()
{
if (_str)
{
delete[]_str;
_str = nullptr;
_capacity = 0;
_size = 0;
}
}
//容量相关操作
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
bool empty()const
{
return 0 == _size;
}
void resize(size_t newsize,char ch)
{
size_t oldsize = _size;
if (newsize > oldsize)
{
//有效元素增多
//多出的元素再空余空间能否放的下
if (newsize > _capacity)
{
reserve(newsize);
}
memset(_str + _size, ch, newsize-oldsize);
}
_size = newsize;
_str[_size] = '\0';
}
void reserve(size_t newcapacity)
{
size_t oldcapacity = _capacity;
if (newcapacity > oldcapacity)
{
//申请新空间
char * temp = new char[newcapacity + 1];
//拷贝元素
strcpy(temp, _str);
//释放旧空间
delete[]_str;
//指向新空间
_str = temp;
_capacity = newcapacity;
}
}
//元素访问相关操作
char& operator[](size_t index)
{
assert(index < _size);
return _str[index];
}
const char& operator[]( int index)
{
assert(index < _size);
return _str[index];
}
//元素修改操作
void push_back(char ch)
{
if (_size == _capacity)
reserve(_capacity * 2);
_str[_size++] = ch;
_str[_size] = '\0';
}
string& operator+=(const char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const string s);
bool operator==(const string s);
bool operator!=(const string s);
bool operator>=(const string s);
bool operator<=(const string s);
bool operator>(const string s);
bool operator<(const string s);
friend ostream& operator<< (ostream& _cout, const bite::string& s)
{
_cout << s.c_str();
return _cout;
}
friend istream operator>>(istream _cin, string s);
//迭代器
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
//特殊操作
size_t find(char ch, size_t pos = 0)
{
for (size_t i = pos; i < _size; i++)
{
if (ch == _str[i])
return i;
}
return npos;
}
size_t rfind(char ch, size_t pos = npos)
{
if (pos == npos)
pos = _size - 1;
for (int i = pos; i >= 0; i--)
{
if (ch == _str[i])
return i;
}
return npos;
}
string substr(size_t pos = 0, size_t n = npos)
{
if (n == npos)
n = _size;
string temp(_str + pos, _str + n + pos);
return temp;
}
const char* c_str()const
{
return _str;
}
private:
size_t _capacity; //当前空间有多大
size_t _size; //当前string里有多少个有效字符
char *_str;
static size_t npos;
};
size_t string::npos = -1;
}
要使用范围for进行打印,必须要给出begin()和end()