怎么样解决浅拷贝中同一块空间被释放多次?在深浅拷贝中我们提到过浅拷贝的两大缺点,并且用现代深拷贝和传统深拷贝去解决它。
当然,我们除了用深拷贝以外,常用的还有引用计数的浅拷贝。
引用计数的浅拷贝
如何实现:我们为每个内存的字符数组添加一个引用计数pcount,即就是在构造函数申请空间的时候多申请出来4个字节。表示有多少个对象使用这块内存,有多少个对象使用,就让pcount值加1,当对象被析构的时候,让pcount值减1,当pcount值为0的时候,将这块内存释放掉。当然pcount也要实现内存共享,所以它也是一个堆中的数据,每个对象都有一个指向它的指针。
但在此时,pcount的类型的选取,就会要有所考虑?
(1)如果选取int类型:(不采取)
#include<iostream>
using namespace std;
class String
{
public:
//构造函数
String(const char* ptr = "")
{
if(ptr == NULL)
{
_ptr = new char[1];
_pcount = 1;
*_ptr = '\0';
}
else
{
_pcount = 1;
_ptr = new char[strlen(ptr)+1];
strcpy(_ptr,ptr);
}
}
//拷贝构造函数
String(String& s)
:_ptr(s._ptr)
,_pcount(s._pcount)
{
_pcount++;
}
//赋值运算符重载
String& operator=(const String& s)
{
if(this != &s)
{
if(--_pcount == 0)
{
delete[] _ptr;
//delete _pcount;
}
else
{
_ptr = s._ptr;
_pcount = s._pcount;
(_pcount)++;
}
}
return *this;
}
//析构函数
~String()
{
if((0 == --_pcount) && _ptr!= NULL)
{
delete[]_ptr;
//delete _pcount;
_ptr = NULL;
}
}
//重载[]
char& operator[](size_t size)
{
if(--_pcount >1)
{
char* ptemp = new char[strlen(_ptr)+1];
int pcount = 1;
strcpy(ptemp,_ptr);
_pcount--;
_ptr = ptemp;
_pcount = pcount;
}
return _ptr[size];
}
private:
char*_ptr;
int _pcount;
};
void FunTest()
{
String s1("hello");
String s2(s1);
String s3(s2);
s3 = s2;
}
测试部分:
#include<iostream>
using namespace std;
#include "String.h"
int main()
{
FunTest();
system("pause");
}
调试一下:
(注意这里我将断点就走到s2,意在说明问题):本来增加s2的时候,两个对象的计数应该是一样的,但是现在一个是1,一个是2,不同步,我们了解到这两个对象的计数变量的地址是不一样的。说明此pcount是公共的,可以被多个对象同时访问。
(2)如果选取的是static类型的:(不采取)
#include<iostream>
using namespace std;
class String
{
public:
//构造函数
String(const char* ptr = "")
{
if(ptr == NULL)
{
_ptr = new char[1];
_pcount = 1;
*_ptr = '\0';
}
else
{
_pcount = 1;
_ptr = new char[strlen(ptr)+1];
strcpy(_ptr,ptr);
}
}
//拷贝构造函数
String(String& s)
:_ptr(s._ptr)
{
_pcount++; //因为是静态的,所以直接进行计数的增值就可以了
}
//赋值运算符重载
String& operator=(const String& s)
{
if(this != &s)
{
if(--_pcount == 0)
{
delete[] _ptr;
//delete _pcount;
}
else
{
_ptr = s._ptr;
_pcount = s._pcount;
(_pcount)++;
}
}
return *this;
}
//析构函数
~String()
{
if((0 == --_pcount) && _ptr!= NULL)
{
delete[]_ptr;
//delete _pcount;
_ptr = NULL;
}
}
//重载[]
char& operator[](size_t size)
{
if(--_pcount >1)
{
char* ptemp = new char[strlen(_ptr)+1];
int pcount = 1;
strcpy(ptemp,_ptr);
_pcount--;
_ptr = ptemp;
_pcount = pcount;
}
return _ptr[size];
}
private:
char*_ptr;
static int _pcount;
};
int String::_pcount = 0;
void FunTest()
{
String s1("hello");
String s2(s1);
String s3(s2);
s3 = s2;
String s4("world");
String s5(s4);
}
测试部分:
#include<iostream>
using namespace std;
#include "String.h"
int main()
{
FunTest();
system("pause");
}
调试一下:
先走到s3,然后走到s4,用s4来构造s5,结果就不对了,走到s4的时候,计数器又变成了1,说明这5个对象公用一个pcount,不能实现引用计数。本质上说,不一样的空间应该有不一样的计数器。
(3)那么我们这样想:如果一个对象第一次开辟空间存放字符串再开辟一块新的空间存放新的额引用计数,当它拷贝构造其它对象时让其它对象的引用计数都指向存放引用计数的同一块空间,pcount设置成int*就可以。
#include<iostream>
using namespace std;
class String
{
public:
//构造函数
String(const char* ptr)
:_ptr(new char[strlen(ptr)+1])
,_pcount(new int(1))
{
strcpy(_ptr, ptr);
}
//拷贝构造函数
String(const String& s)
:_ptr(s._ptr)
,_pcount(s._pcount)
{
(*_pcount)++;
}
//赋值运算符重载
String& operator=(const String& s)
{
if (_ptr != s._ptr)
{
if (--(*_pcount) == 0)
{
delete[] _ptr;
delete _pcount;
}
_ptr = s._ptr;
_pcount = s._pcount;
(*_pcount)++;
}
return *this;
}
//析构函数
~String()
{
if (--(*_pcount) == 0)
{
cout << "delete[]" << _ptr << endl;//测试
delete[]_ptr;
delete _pcount;
_ptr = NULL;
_pcount = NULL;
}
}
private:
char*_ptr;
int* _pcount;
};
void FunTest()
{
String s1("hello world");
String s2(s1);
String s3(s2);
String s4(s3);
String s5("change world");
String s6(s5);
}
测试部分:
#include<iostream>
using namespace std;
#include "String.h"
int main()
{
FunTest();
system("pause");
}
调试一下:
运行结果
监视窗口:
到这里就实现了两块不一样的空间拥有不同的计数器。但是这种方式有缺陷。
缺陷一:每次new两块空间,创建多个对象的时候效率比较低
缺陷二:它多次分配小块空间,容易造成内存碎片化,导致分配不出来大块内存。
(4)还能在优化 :仿照new的底层实现,开辟一块空间,但是它的头几个字节用于计数。
#include<iostream>
using namespace std;
class String
{
public:
String(char *ptr = "")
{
if(ptr == NULL)
{
_ptr = new char[strlen(ptr)+5];
_ptr = new char[5];
*(_ptr+4) = '\0';
}
else
{
_ptr = new char[strlen(ptr)+5];
*((int*)_ptr) = 1;
_ptr += 4;
strcpy(_ptr,ptr);
}
}
String(const String& s)
:_ptr(s._ptr)
{
(*((int*)(_ptr-4)))++;
}
String& operator=(const String& s)
{
if(this != &s)
{
if(--(*((int*)(_ptr-4))) == 0)
{
delete[]_ptr;
}
else
{
_ptr = s._ptr;
++(*(int*)(_ptr-4));
}
}
return *this;
}
~String()
{
if((_ptr != NULL) && ((--(*((int*)(_ptr-4)))) == 0))
{
delete[]_ptr;
_ptr = NULL;
}
}
private:
char* _ptr;
};
void Funtest()
{
String s1("hello");
String s2(s1);
String s3(s2);
s3 = s2;
}
测试部分:
#include<iostream>
using namespace std;
#include "String.h"
int main()
{
FunTest();
system("pause");
}
写时拷贝
到这里就实现了引用计数的浅拷贝,引用计数的浅拷贝能够提高效率,实现浅拷贝中空间的正确释放,但是引用计数的浅拷贝的缺点也是浅拷贝的另一种缺点,就是空间的值不能被修改,会互相影响。这时候我们就用到了写时拷贝,写时拷贝也叫延时拷贝,就是当你需要改变空间的值时,我就给你单独开辟出一块空间让你去用,不需要改变空间的值时,我就不开辟。
深拷贝是直接用创建空间的方法解决了浅拷贝的两大缺点,引用计数的浅拷贝相比较深拷贝而言,不用主动创建空间,而是需要的时候才开辟空间,提高了代码的效率。
#include<iostream>
using namespace std;
#include<assert.h>
class String
{
public:
//构造函数
String(const char* ptr)
:_pcount(new int(1))
{
_size = strlen(ptr);
_capa = _size;
_ptr = new char[_capa + 1];
strcpy(_ptr, ptr);
}
//拷贝构造函数
String(const String& s)
:_ptr(s._ptr)
,_pcount(s._pcount)
, _size(s._size)
, _capa(s._capa)
{
(*_pcount)++;
}
//赋值运算符重载
String& operator=(const String& s)
{
if (_ptr != s._ptr)
{
if (--(*_pcount) == 0)
{
delete[] _ptr;
delete _pcount;
}
_ptr = s._ptr;
_pcount = s._pcount;
(*_pcount)++;
}
return *this;
}
void CopyOnWrite()//检测是否独占空间,否则进行处理
{
if (*_pcount > 1)
{
char *newptr = new char[_capa + 1];
strcpy(newptr, _ptr);
(*_pcount)--;
_ptr = newptr;
_pcount = new int(1);
}
}
void Expand(size_t n)//扩容
{
if (n > _capa)
{
_ptr = (char*)realloc(_ptr, n + 1);
assert(_ptr);
_capa = n;
}
}
void PushBack(char ch)//尾插一个字符
{
CopyOnWrite();
if (_size == _capa)
{
Expand(_capa * 2);
}
_ptr[_size++] = ch;
_ptr[_size] = '\0';
}
char& operator[](size_t pos)//可读可写
{
CopyOnWrite();
return _ptr[pos];
}
char operator[](size_t pos) const //只读
{
return _ptr[pos];
}
const char* Getptr()
{
return _ptr;
}
void Release()//清理工作
{
if (--(*_pcount) == 0)
{
//cout << "delete[]" << _ptr << endl;//测试
delete[]_ptr;
delete _pcount;
_ptr = NULL;
_pcount = NULL;
}
}
//析构函数
~String()
{
Release();
}
private:
char*_ptr;
int* _pcount;
size_t _size;
size_t _capa;
};
void FunTest()
{
String s1("hello world");
String s2(s1);
cout << s1[0] << endl;
cout << s1.Getptr() << endl;
cout << s2.Getptr() << endl;
}
程序运行结果: