要了解C++中的浅拷贝,深拷贝,写时拷贝,我们先来根据以下步骤来逐步了解:
- 什么是浅拷贝? 浅拷贝会出现什么问题? 什么是深拷贝?
- 实现String类深拷贝-普通版
- 实现String类深拷贝—简洁版
- 什么是引用计数,用引用计数实现String时,引用计数可以用普通的成员变量和类的静态成员变量吗? 为什么?
- 完成引用计数版本的String类—该引用计数也属于浅拷贝
- 完成COW(写时拷贝版的String)
一、什么是浅拷贝? 浅拷贝会出现什么问题? 什么是深拷贝?
浅拷贝:
模拟实现一段简单的浅拷贝String类
#include<ostream>
using namespace std;
//浅拷贝
class String
{
public:
String(const char* pStr = "")
{
if(NULL == *pStr)
{
_pStr = new char[1];
*_pStr = '\0';
}
else
{
_pStr = new char[strlen(pStr)+1];
strcpy(_pStr,pStr);
}
}
String(const String& s)
{
if(this != &s)
_pStr = s._pStr;
}
String& operator = (const String& s)
{
if(this != &s)
_pStr = s._pStr;
return *this;
}
~String()
{
if(NULL != _pStr)
{
delete[] _pStr;
_pStr = NULL;
}
}
private:
char* _pStr;
};
int main()
{
String s1 = "hello world";
String s2(s1);
system("pause");
return 0;
}
程序运行结果调试:
①创建变量s1,s2后:
②调用析构函数销毁s2时:
③调用析构函数销毁s1时:
浅拷贝存在的问题:
浅拷贝也称位拷贝,是指源对象与拷贝对象共用一份实体,仅仅是引用的变量不同(名称不同)。对其中任何一个对象的改动都会影响另外一个对象。对浅拷贝来说,编译器只是直接将指针的值拷贝过来,当多个对象共用同一块内存,当一个对象将这块内存释放掉之后,另一些对象不知道该块空间已经还给了系统,以为还有效,所以在对这段内存进行操作的时候,会发生访问违规。
【注意】:
当在一个自定义的类中,没有给出构造函数,重载赋值运算符的时候,一般编译器就会以浅拷贝的形式自动合成。
二、实现String类深拷贝-普通版
深拷贝:
深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。
实现String类深拷贝-普通版:
#include<ostream>
using namespace std;
//深拷贝---普通版
class String
{
public:
String(const char* pStr = "")
{
if(NULL == *pStr)
{
_pStr = new char[1];
*_pStr = '\0';
}
else
{
_pStr = new char[strlen(pStr)+1];
strcpy(_pStr,pStr);
}
}
String(const String& s)
:_pStr(new char[strlen(s._pStr)+1])
{
if(this != &s)
{
strcpy(_pStr,s._pStr);
}
}
String& operator = (const String& s)
{
//这里最好使用这种创建临时变量来拷贝s的做法,再释放空间
//目的是为了防止直接使用this指针进行操作,万一申请空间失败,就会造成内存泄漏的问题
if(this != &s)
{
char* temp = new char[strlen(s._pStr)+1];
strcpy(temp,s._pStr);
delete[] _pStr;
_pStr = NULL;
_pStr = temp;
}
return *this;
}
~String()
{
if(NULL != _pStr)
{
delete[] _pStr;
_pStr = NULL;
}
}
private:
char* _pStr;
};
int main()
{
String s1 = "hello world";
String s2(s1);
system("pause");
return 0;
}
程序运行结果并不会在崩溃:
1、
2、
三、实现String类深拷贝—简洁版
#include<ostream>
#include<stdio.h>
#include<stdlib.h>
using namespace std;
//深拷贝---简洁版
class String
{
public:
String(const char* pStr = "")
{
if(NULL == *pStr)
{
_pStr = new char[1];
*_pStr = '\0';
}
else
{
_pStr = new char[strlen(pStr)+1];
strcpy(_pStr,pStr);
}
}
String(const String& s)
:_pStr(NULL) // 一定要初始化,防止释放随机值的空间
{
String temp(s._pStr);
swap(_pStr,temp._pStr);
}
String& operator = (const String& s)
{
if(this != &s)
{
String temp(s._pStr);
swap(_pStr,temp._pStr);
}
return *this;
}
~String()
{
if(NULL != _pStr)
{
delete[] _pStr;
_pStr = NULL;
}
}
private:
char* _pStr;
};
int main()
{
String s1 = "hello world";
String s2(s1);
system("pause");
return 0;
}
四、什么是引用计数,用引用计数实现String时,引用计数可以用普通的成员变量和类的静态成员变量吗? 为什么?
方案1:普通成员变量
方案2:类的静态成员变量
方案3:引用计数
引用计数:
在引用计数中,每一个对象负责维护对象所有引用的计数值。当一个新的引用指向对象时,引用计数器就递增,当去掉一个引用时,引用计数就递减。当引用计数到零时,该对象就将释放占有的资源。
当使用方案1时,引用计数只是一个普通的成员变量,也就是每一份对象都拥有一个自己的计数器变量,在新的类对象拷贝构造和赋值的时候需要进入原来的类对象中去获取这个计数器,再进行修改,在同一个类对象之间缺乏通用性。
当使用方案2时,引用计数是一个类的静态成员变量,类的对象都共享一个计数值,例如:
String s1 = "hello";
String s2(s1);
String s3 = "world";
这时就会发现s1里的计数变量会从2变成1,被s3改变,运行下来会导致程序崩溃。
五、完成引用计数版本的String类—该引用计数也属于浅拷贝
先看一个分析图:
程序:
class String
{
public:
String(const char* pStr = "")
{
if(NULL == *pStr)
{
_pStr = new char[1+4];
_pStr+=4;
*_pStr = '\0';
}
else
{
_pStr = new char[strlen(pStr)+1+4];
_pStr+=4;
strcpy(_pStr,pStr);
}
GetRef() = 1;
}
String(const String& s)
{
if(_pStr != s._pStr)
{
_pStr = s._pStr;
++GetRef();
}
}
String& operator = (const String& s)
{
if(_pStr != s._pStr)
{
Release();
_pStr = s._pStr;
++GetRef();
}
return *this;
}
~String()
{
Release();
}
int& GetRef()//得到引用计数指针域里的计数值
{
return *((int *)_pStr-1);
}
void Release()//释放_pStr 空间
{
if(_pStr && (0 == --GetRef()))
{
_pStr -= 4;
delete[] _pStr;
_pStr = NULL;
}
}
private:
char* _pStr;
};
int main()
{
String s1 = "hello";
String s2(s1);
String s3 = "world";
s3 = s2;
system("pause");
return 0;
}
结论:
引用计数版的浅拷贝类型String类,还存在一个问题,就是不同对象共享同一块空间,当其中一个对象改变了该内存块中的内容时,指向该内存块的所有对象的值都会被改变。
六、完成COW(写时拷贝版的String)
程序:
#include<ostream>
#include<stdio.h>
#include<stdlib.h>
using namespace std;
//copy_on_write
class String
{
public:
String(const char* pStr = "")
{
if(NULL == *pStr)
{
_pStr = new char[1+4];
_pStr+=4;
*_pStr = '\0';
}
else
{
_pStr = new char[strlen(pStr)+1+4];
_pStr+=4;
strcpy(_pStr,pStr);
}
GetRef() = 1;
}
String(const String& s)
{
if(_pStr != s._pStr)
{
_pStr = s._pStr;
++GetRef();
}
}
String& operator = (const String& s)
{
if(_pStr != s._pStr)
{
Release();
_pStr = s._pStr;
++GetRef();
}
return *this;
}
~String()
{
Release();
}
char& operator[](size_t index)
{
if(GetRef()>1)
{
char* temp = new char[strlen(_pStr)+1+4];
*((int*)temp) = 1;
temp += 4;
strcpy(temp,_pStr);
--GetRef();
_pStr = temp;
}
return _pStr[index];
}
int& GetRef()
{
return *((int *)_pStr-1);
}
void Release()
{
if(_pStr && (0 == --GetRef()))
{
_pStr -= 4;
delete[] _pStr;
_pStr = NULL;
}
}
private:
char* _pStr;
};
int main()
{
String s1 = "hello";
String s2(s1);
String s3 = "world";
s3 = s2;
s2 = "sss";
system("pause");
return 0;
}