先修改str1,再修改str2:
/*************************************************************************
> File Name: string_copy_on_write.cpp
> Author:
> Mail:
> Created Time: 2016年03月25日 星期五 20时22分39秒
************************************************************************/
#include <iostream>
#include <string>
#include <stdio.h>
using namespace std;
int main()
{
string str1 = "linux_ever";
string str2 = str1;
cout << "查看str1和str2中内容指向的地址: " << endl;
printf("str1's address is %x\n", str1.c_str());
printf("str2's address is %x\n", str2.c_str());
cout << "\n输出两个string对象的值之后的内存地址:" << endl;
cout << "str1 = " << str1 << endl;
cout << "str2 = " << str2 << endl;
printf("str1's address is %x\n", str1.c_str());
printf("str2's address is %x\n", str2.c_str());
cout << "\n修改了str1之后的地址:" << endl;
str1[0] = 'u';
printf("str1's address is %x\n", str1.c_str());
printf("str2's address is %x\n", str2.c_str());
cout << "\n修改了str2之后的地址:" << endl;
str2[0] = 'w';
printf("str1's address is %x\n", str1.c_str());
printf("str2's address is %x\n", str2.c_str());
return 0;
}
输出:
先修改str2,再修改str1:
/*************************************************************************
> File Name: string_copy_on_write.cpp
> Author:
> Mail:
> Created Time: 2016年03月25日 星期五 20时22分39秒
************************************************************************/
#include <iostream>
#include <string>
#include <stdio.h>
using namespace std;
int main()
{
string str1 = "linux_ever";
string str2 = str1;
cout << "查看str1和str2中内容指向的地址: " << endl;
printf("str1's address is %x\n", str1.c_str());
printf("str2's address is %x\n", str2.c_str());
cout << "\n输出两个string对象的值之后的内存地址:" << endl;
cout << "str1 = " << str1 << endl;
cout << "str2 = " << str2 << endl;
printf("str1's address is %x\n", str1.c_str());
printf("str2's address is %x\n", str2.c_str());
cout << "\n修改了str2之后的地址:" << endl;
str2[1] = 'u';
printf("str1's address is %x\n", str1.c_str());
printf("str2's address is %x\n", str2.c_str());
cout << "\n修改了str1之后的地址:" << endl;
str1[1] = 'w';
printf("str1's address is %x\n", str1.c_str());
printf("str2's address is %x\n", str2.c_str());
return 0;
}
输出结果:
==================================================================================================================================================================
写时拷贝的两种方案
2017年07月09日 18:07:00 Chars-1 阅读数:1885
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/m0_37956168/article/details/74898047
1. 什么是写时拷贝
写时拷贝故名思意:是在写的时候(即改变字符串的时候)才会真正的开辟空间拷贝(深拷贝),如果只是对数据的读时,只会对数据进行浅拷贝。
写时拷贝:引用计数器的浅拷贝,又称延时拷贝
:写时拷贝技术是通过"引用计数"实现的,在分配空间的时候多分配4个字节,用来记录有多少个指针指向块空间,当有新的指针指向这块空间时,引用计数加一,当要释放这块空间时,引用计数减一(假装释放),直到引用计数减为0时才真的释放掉这块空间。当有的指针要改变这块空间的值时,再为这个指针分配自己的空间(注意这时引用计数的变化,旧的空间的引用计数减一,新分配的空间引用计数加一)。
2. string中的两种写时拷贝
一:
- 动态开辟两个空间一个用来存放字符串:_str,一个用来存放计数器_refCountPtr
- 每次拷贝构造时(赋值运算符的重载后半段),直接把字符串的指针付给新的String,然后给计数器加加(*_refCountPtr)++
- 在释放String的时候,先对计数器减减,再判断计数器是否为零(即看是否还有指针共享此内存),若计数器为零则直接释放_str 和 _refCountPtr
- 在对字符串进行更改的时候(写时),就要进行深拷贝:先进行步骤3,在进行深拷贝和字符串的更改
class String//写时拷贝
{
public:
String(char * str = "\0")
:_refCountPtr(new int(1))//开辟计数器动态内存
,_size(strlen(str))
{
_capacity = _size;
_str = new char[_capacity+1];//开辟字符串动态内存
strcpy(_str,str);
}
String(String& s)//拷贝构造
:_str(s._str)//直接浅拷贝
,_refCountPtr(s._refCountPtr)
,_size(s._size)
,_capacity(s._capacity)
{
(*_refCountPtr)++;//但对计数器 ++
}
~String()//析构
{
Release();
}
inline void Release()
{
if(--(*_refCountPtr) == 0)//计数器--,并判断是否为0
{
cout<<"~String"<<_str<<endl;
delete[] _str;
delete _refCountPtr;//释放
}
}
String &operator=(String &s)//重载
{
if(_str != s._str)//判断是否为自己给自己赋值
{
Release();//判断并处理this
_str = s._str;
_refCountPtr = s._refCountPtr;
_size = s._size;
_capacity = s._capacity;
(*_refCountPtr)++;
}
return *this;
}
char *c_str() const
{
return _str;
}
//写时拷贝
String &push_back(char ch)
{
char* str = new char[_capacity*2];
strcpy(str,_str);
//先将原来的数据保存,用于给后面重新开辟的空间赋值
Release();//处理原来的空间
_str = str;
_str[_size++] = ch;
_str[_size] = '\0';
_capacity *= 2;
_refCountPtr = new int(1);//
return *this;
}
private:
char *_str;
int *_refCountPtr;
size_t _size;
size_t _capacity;
};
二:
开辟一个空间,前面4个字节为计数器count,剩下的为字符串_str的空间,用法与分开相同。
- 1
class String
{
public:
String(char *str = "\0")
:_capacity(strlen(str))
{
_size = _capacity;
_str = new char[_capacity + 5];//多开辟了4个字节
*(int *)_str = 1;//前4个自己存放计数器
_str += 4; //使_str指向开辟的4个字节后的空间
strcpy(_str,str);//拷贝字符串
}
String(String &s)//拷贝构造
:_str(s._str)//直接浅拷贝
,_capacity(s._capacity)
,_size(s._size)
{
++(*(int *)(_str-4));//(str前的4个字节为计数器) ++
}
~String()//析构
{
Release();
}
void Release()
{
if(--(*(int *)(_str-4)) == 0)//计数器-1判断是否为零
{
cout<<"~String "<<_str<<endl;
delete[] (_str-4);
}
}
String& operator=(String &s)//赋值重载
{
if(_str != s._str)//判断是否为自己为自己赋值
{
Release();//处理原指针(释放空间,或者计数器-1)
_str = s._str;
_size = s._size;
_capacity = s._capacity;//赋值
++(*(int *)(_str-4));//改变先指针的计数器
}
return *this;
}
String &operator+=(char ch)//重载,会改变,会深拷贝
{
char* str = new char[_capacity*2 + 6];
strcpy(str+4, _str);//先保存原字符串
Release();//因为要重新开辟空间,所以需要处理以前的空间
*(int *)str = 1;//新开辟的计数器置1
_str = str + 4;
_str[_size++] = ch;
_str[_size] = '\0';
_capacity *= 2;
return *this;
}
String &append(const char *str)//追加字符串
{
size_t len = strlen(str);//方法同+=的重载
_capacity = len + _size;
char *tmp = new char[_capacity + 6];
strcpy(tmp+4 ,_str);
Release();
*(int *)tmp = 1;
_str = tmp + 4;
strcpy(_str+_size, str);
_size = _capacity;
return *this;
}
private:
char* _str;//开辟多4个,指向4个字节后的地址
size_t _size;
size_t _capacity;
};
==================================================================================================================================================================
C++写时拷贝
2018年08月14日 19:57:14 _来信 阅读数:66
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/han8040laixin/article/details/81672388
一:浅拷贝
一个类,如果不写拷贝构造函数,那么它的默认拷贝构造函数为浅拷贝,浅拷贝有什么问题呢?
拿一个简单的String类举例:
class String{
public:
String(char* str = "\0")
:_str(new char[strlen(str)+1])
{
strcpy(_str, str);
}
~String()
{
if (_str){
delete[] _str;
}
}
private:
char* _str;
void Test()
{
String s1("hello String!");
String s2(s1);
}
在Test函数里,我创建了s1对象,然后s2对象通过s1来拷贝构造,由于没有写拷贝构造函数,所以为浅拷贝,运行程序直接崩溃:
原因是什么呢?
因为是浅拷贝,所以两个对象的_str指针同时指向了一块空间,然后这两个对象生命周期结束时,都会调用析构函数,那么这一块空间就被析构了两次,所以会崩溃;
还有一个问题,如果改变s1对象的指针指向的空间,由于两对象的指针指向同一块空间,所以s2对象的指针所指向的空间也一块变了,这不是我们想看到的,两对象应该各是各的。
大白话总结一下浅拷贝的问题:
1.析构多次;
2.一个改变影响另一个。
二:深拷贝可以解决浅拷贝的问题
什么是深拷贝,就是我拷贝你的时候,重开一块空间,然后把你的数据复制到我的空间里,咱俩各是各的,以后就不影响了。
代码实现:
String(const String& s)
:_str(new char[strlen(s._str)+1])
{
strcpy(_str, s._str);
}
三:深拷贝要拷贝数据,代价过大(所以才有写时拷贝)
我们可以既做到浅拷贝,又能解决析构时一块空间被释放多次,那就是引用计数。
private:
char* _str;
int* _refCount;
_refCount指向的空间就专门用来存_str指向的空间同时被多少对象指向。
①构造对象时,new出来的空间当然只被_str指向,所以引用计数初始化为1。
String(const char* str = "")
:_str(new char[strlen(str) + 1])
, _refCount(new int(1))
{
strcpy(_str, str);
}
②拷贝构造时,多出来一个对象的指针指向空间,所以引用计数要加一。
String(String& s)
{
this->_str = s._str;
_refCount = s._refCount;
++(*this->_refCount);
}
③ operator= 同样是需要拷贝,所以又多出来了一个对象指向空间,引用计数再加一
和拷贝构造不一样,operator=是把一个已经构造好的对象重新赋值,而拷贝构造是构造一个新对象,所以在operator之前,我们需要先考虑之前构造好的对象它的引用计数是多少?
.如果之前的对象引用计数为1,我们给又给它重新赋值,所以之前的对象的指针指向的空间就应该被释放了:
.如果之前的对象引用计数为大于1,就算重新赋值了,也还有其他对象的指针指向这块空间,所以不需要释放。
String& operator=(const String& s)
{
if (this != &s)
{
if ((*_refCount) == 1){
delete[] _str;
delete _refCount;
}
_str = s._str;
_refCount = s._refCount;
(*_refCount)++;
}
return *this;
}
使用引用计数,重要的是析构函数怎么写,每一次析构都要给引用计数减1,只有当引用计数为1时,才释放空间。
~String()
{
if (--(*_refCount) == 0)
{
delete[] this->_str;
delete this->_refCount;
}
}
到目前为止,已经通过引用计数解决了浅拷贝的析构多次会崩溃的问题,但是还是没有解决一个改变影响另一个的问题。
四:通过写时拷贝来解决浅拷贝的一个改变影响另一个的问题
什么是写时拷贝,就是写的时候才拷贝,也就是说,你如果要改变,就需要拷贝一份,改变的是拷贝的这一份。
什么时候可能会改变String类的数据呢?
char& operator[](size_t pos)
{
return _str[pos];
}
operator[]时,我返回引用,这样你可以通过我的返回值来改变我的_str指针指向的数据了。
写时拷贝实现:
void CopyOnWrite()
{
if (*_refCount > 1){//如果引用计数为1,直接写不影响
char* tmp = new char[strlen(_str) + 1];
strcpy(tmp, _str);
(*_refCount)--;
str = tmp;
}
}
接下来我把所有用户可能通过用来改变数据的接口先写时拷贝一份就好了,比如:
char& operator[](size_t pos)
{
CopyOnWrite();
return _str[pos];
}