浅拷贝+引用计数--写时拷贝---模拟实现string容器

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/liuyuchen282828/article/details/102760965

引用计数

深拷贝
多个对象共享同一份资源时,最后能够保证该资源只被释放一次
应该由哪个对象释放资源?
由最后一个使用该资源的对象去释放
怎么知道一个对象是最后一个使用该资源的对象?
给一个计数,记录使用该资源对象的个数

实现

计数用普通整型

先来看一个例子

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()

猜你喜欢

转载自blog.csdn.net/liuyuchen282828/article/details/102760965