之前的博客已经给出了如何自己定义一个string类,以及其内部应该有的操作,今天就让我们根据STL库中给出的string来看看,它重要的写实拷贝实现和一点点读时拷贝(也是写时拷贝)

1、写时拷贝(copy-on-write)
class String
{
public:
String(const String &str)
:_pData(NULL)
{
String temp(str._pData);
swap(_pData,temp._pData);
}
private:
char *_pData;
}
void test()
{
String s1("hello world");//构造
String s2(s1);//拷贝构造
}这里面实现的是用空间换时间的一种方法,定义极其简单,然而大神们写出来的STL库中的string是更为精巧的。就是应用了写实拷贝的技术,防止浅拷贝发生,并且还省了空间,
那么问题来了????
Q:什么是写时拷贝呢?
A:写时拷贝就是一种拖延战术,当你真正用到的时候才去给它开辟空间,不然它只是看起来存在,实际上只是逻辑上的存在,这种方法在STL的string中体现的很明显。
由于string类是用char* 实现的,其内存都是在堆上开辟和释放的。堆上的空间利用要很小心,所以当你定义一个sring类的对象,并且想对这个对象求地址,其返回值是const char*类型,onlyread属性哦,如果还想对该地址的内容做什么改变,只能通过string给的方法去修改。
举个栗子:
#include#include using namespace std; int main() { string s1("再来一遍:hello world"); string s2(s1); //c++方式打印一个字符串的地址!!!!! //static_cast---c++中的强制类型转换,不检查 //string的c_str()方法返回值是const char * cout< (s1.c_str())< (s2.c_str())< _<)~~~~我也这么觉得 printf("%x\n",s1.c_str()); printf("%x\n",s2.c_str()); }
(vs2010版本)
结果是不是和你想的不一样。。。。(明明应该不变的说~)
vc 6.0版本下:s1,s2的地址是一样的。这里就不进行截屏了,如果有兴趣的同学,下去可以试试哈~
那么当对s1,s2进行修改时是怎么样的呢
s1[0]='h'; s2[0]='w';
(vs2010版本)
VC6.0版本下:s1,s2的地址不一样(同vs2010版本)
所以我们得出的结论是:
当对string对象只进行拷贝构造时,发生的是写时拷贝(假拷贝),只有对其对象进行修改时(有写的操作),才对其对象另外开辟空间,进行修改。
要想达到这样的效果,在一定程度上节省了空间。
必须做到两点:内存的共享,写时拷贝。
(1)copy-on-write的原理?
“引用计数”,程序猿就是这般机智~~~~
当对象s1实例化,调用构造,引用计数初始化=1;
当有对象对s1进行拷贝时,s1的引用计数+1;
当有对象是由s1拷贝来的或者是s1自身进行析构是,s1的引用计数进行-1;
当有对象是由s1拷贝来的或者是s1自身需要修改时,进行真拷贝,并且引用计数-1;
当引用计数==0的时候,进行真正的析构。
(2)引用计数应该如何设计在?
关于引用计数的实现,你是不是也有这样的疑惑呢?
当类的对象之间进行共享时,引用计数也是共享的
当类中的对象从公共中脱离出来,引用计数就是它自己的了。
那么如何做到从独立--->共享--->独立的呢???
如果你想将引用计数当做String类的成员变量,那么什么样的类型适合它呢?
int _count; 那么每个对象的实例化都拥有一个自己的引用计数,无法实现共享
class String
{
public:
String(pData=NULL)
:_pData(new char[strlen(pData)+1])
,_count(1)
{
strcpy(_pData,pData);
}
~String()
{
if(--_count==0)
{
delete []_pData;
}
}
String(String &str)
:_pData(str._pData)
{
str._count++;
_count=str._count;
}
private:
char *_pData;
int _count;
};
string s1="hello world";
string s2(s1);
//s1构造,s2拷贝构造:s1和s2指向同一空间,s1和s2的_count都变成2
//当s2先析构,s2的_count--变成1,不释放
//当s1析构时,s1的_count--变成1,不释放
//造成内存泄露static int _pCount;那么每个对象的实例化都拥有这唯一的一个引用计数,共享范围过大
class String
{
public:
String(pData=NULL)
:_pData(new char[strlen(pData)+1])
{
_count=1;
strcpy(_pData,pData);
}
~String()
{
if(--_count==0)
{
delete []_pData;
}
}
String(String &str) //不加const,不然底下的浅拷贝会出错
:_pData(str._pData)
{
str._count++;
}
private:
char *_pData;
static int _count; //静态的成员变量要在类外进行初始化
};
int String::_count=0;
string s1="hello world";
string s2(s1);
string s3("error");
//s1构造,s2拷贝构造:s1和s2指向同一空间,_count都变成2
//s3构造,_count变成1
//当s3先析构,_count--变成0,释放
//s1,s2造成内存泄露int *_pCount;可以实现引用计数。
class String
{
public:
String(pData=NULL)
:_pData(new char[strlen(pData)+1])
,_pCount(new int(1))
{
strcpy(_pData,pData);
}
~String()
{
if(--(*_pCount)==0)
{
delete []_pData;
delete _pCount;
}
}
String& operator=(const String *str)
{
if(_pData!=str._pData)
{
if(--(*_pCount)==0)
{
delete _pCount;
delete []_pData;
}
(*str._pCount)++;
_pCount=str._pCount;
_pData=str._pData;
}
return *this;
}
String(String &str) //不加const,不然底下的浅拷贝会出错
:_pData(str._pData)
,_pCount(str._pCount)
{
(*str._pCount)++;
}
private:
char *_pData;
int *_pCount;
};这些字符串都是在堆上开辟的,那么引用计数也可以在堆上开辟,要从逻辑上,看引用计数是个指针,存次数,从物理上看,引用计数应该和字符指针放在一起,便于管理。让数据相同的对象都可以共享同一片内存。
综上,引用计数的设计如图:
(3)引用计数什么时候需要共享呢?
情况1:string s2(s1); //s2拷贝自s1,即s2中的数据和s1的一样
情况2:string s2; s2=s1;//s2的数据由s1赋值而来,即s2中的数据和s1的一样
综上所述:
string类中的拷贝构造和赋值运算符重载需要引用计数
(4)什么情况下需要进行写时拷贝
对内容有修改时
(5)c++版实现代码
class String
{
private:
char *_pData; //引用计数存在于_pData[-1]
public:
//构造函数
String(pData=NULL)
:_pData(new char[strlen(pData)+1+sizeof(int)])
{
//强转在头上4个字节存放引用计数的值
(*(int *)_pData)=1;
//恢复其字符串的长度
_pData+=4;
strcpy(_pData,pData);
}
//拷贝构造
String(const String&str)
:_pData(str. _pData)
{
//(*(--(int *)str._pData)) ++;
//这个版本是错的,大家看看错在哪里?可以留言告诉我哦
(*(--(int*)_pData-1)++;
}
//赋值运算符重载
String& operator=(const String &str)
{
if(_pData!=str._pData)
{
if(--(*(--(int*)_pData-1)==0)
{
_pData-=4;
delete []_pData;
}
else
{
_pData=str._pData;
(*(--(int*)_pData-1)++;
}
}
return *this;
}
//析构函数
~String()
{
if(--(*(--(int*)_pData-1)==0)
{
_pData-=4;
delete []_pData;
}
}
};2、读时拷贝(copy-on-read)
当C++的STL库中的string被这么利用时:
string s1="hello world"; long begin = getcurrenttick(); for(size_t i=0;i创新互联www.cdcxhl.cn,专业提供香港、美国云服务器,动态BGP最优骨干路由自动选择,持续稳定高效的网络助力业务部署。公司持有工信部办法的idc、isp许可证, 机房独有T级流量清洗系统配攻击溯源,准确进行流量调度,确保服务器高可用性。佳节活动现已开启,新人活动云服务器买多久送多久。
当前文章:通过STL中的string看写时拷贝和读时拷贝-创新互联
标题URL:http://www.jxjierui.cn/article/dopcci.html


咨询
建站咨询
