引用计数
深拷贝
多个对象共享同一份资源时,最后能够保证该资源只被释放一次
应该由哪个对象释放资源?
由最后一个使用该资源的对象去释放
怎么知道一个对象是最后一个使用该资源的对象?
给一个计数,记录使用该资源对象的个数
实现
计数用普通整型
先来看一个例子
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是不是0if (_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是不是0if (_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是不是0if (_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是不是0if (_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是不是0if (_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个字符chstring(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()