C++String类与深浅拷贝
浅拷贝
编译器默认合成的拷贝构造和赋值运算符是浅拷贝,意思是从编译器的角度看到什么就原封不动拷贝到当前对象。
导致的问题:多个对象使用同一块空间,释放的空间被其他对象访问会导致程序崩溃。
以下实现深拷贝版本,将对象动态申请的资源也拷贝到当前对象。
class String {
public:
String(const char* str = "")
{
if (NULL == str) {
str = "";
}
_pStr = new char[strlen(str) + 1];
strcpy(_pStr, str);
}
//深拷贝构造
String(const String& s)
:_pStr(new char[strlen(s._pStr) +1])
{
strcpy(_pStr, s._pStr);
}
String& operator=(String s) {
swap(_pStr, s._pStr);
return *this;
}
//四步赋值版本
//String& operator=(const String& s)
//{
// if (this != &s) {
// char* pStr = new char[strlen(s._pStr) + 1];
// strcpy(pStr, s._pStr);
// delete[] _pStr;
// _pStr = pStr;
// }
// return *this;
//}
~String()
{
if (_pStr) {
delete[] _pStr;
}
}
String operator+(String s) {
int len = size() + s.size()+1;
char* tmp = new char[len];
strcpy(tmp, _pStr);
strcat(tmp, s._pStr);
swap(tmp, s._pStr);
delete[] tmp;
return s;
}
bool operator==(const String &str)const
{
if (strcmp(_pStr, str._pStr) == 0) {
return true;
}
return false;
}
size_t size() const
{
return strlen(_pStr);
}
const char* c_str() const
{
return _pStr;
}
//取从position所指位置连续取len个字符组成子串返回
String& sub_str(int position, int len) {
if (position<0 || position >= size() || len<0 || len >size()) //参数不合理,不取子串
{
}
else
{
if (position + len - 1 >= size()) //字符串不够长
len = size() - position;
for (int i = 0, j = position; i<len; i++, j++)
_pStr[i] = _pStr[j];
_pStr[len] = '\0';
}
return *this;
}
private:
char* _pStr;
};
测试用例:
String s0;
String s1("hello");
String s2(s0);
String s3 = s1;
s2 = s1;
s3 = s1 + s2;
s3 = s3 + s0;
s3.sub_str(0, 4);
/*错误版本*/
//交换了指针之后会调用析构函数,会释放const修饰的对象空间
String(const String& s)
{
String strTmp(s._pStr);
swap(_pStr, strTmp._pStr);
}
浅拷贝的引用计数版
为了解决浅拷贝的资源管理问题,提出了引用计数,在每个对象里保存关于资源被引用的次数,如果在对象析构时,发现引用计数>0,则不需要释放资源。
具体实现:
思路1:用一个静态成员变量保存计数,让所有该类对象可以访问。错误!当对象没有调用拷贝构造,而是普通构造函数,它的计数和其他引用了资源的对象造成二义性。
思路2:每个对象保存一个计数指针,给每一块空间分配一个计数,拷贝构造时复制指针,析构时–自己指针的引用计数。可行!
思路3:在字符串空间前预先分配四字节用来计数,以下基于此实现!
class String {
public:
String(const char* str = "")
{
if (NULL == str) {
str = "";
}
_pStr = new char[strlen(str) + 1 + 4];
_pStr += 4;
get_ref_count() = 1;
strcpy(_pStr, str);
}
String(const String& s)
:_pStr(s._pStr)
{
++get_ref_count();
}
String& operator=(const String& s) {
if (this != &s) {
_Release();
_pStr = s._pStr;
++get_ref_count();
}
return *this;
}
void _Release()
{
--get_ref_count();
if (get_ref_count() == 0 && _pStr) {
delete[] (_pStr-4);
}
}
~String()
{
if (_pStr) {
_Release();
}
}
int& get_ref_count()
{
return *(int*)(_pStr - 4);
}
//返回值修改内容,写时拷贝
char& operator[](size_t index)
{
//有两个以上对象引用了这块空间,不能修改其他对象的值
if (get_ref_count() > 1) {
--get_ref_count();
String strTmp(_pStr);
_pStr = NULL;
swap(_pStr, strTmp._pStr);
}
return _pStr[index];
}
//[]做右值
const char& operator[](size_t index)const
{
return _pStr[index];
}
private:
char* _pStr;
};
由于引用计数出现,必须要保证对计数和资源的操作必须是原子的,存在竟态条件。所以在多线程下这个类还不完善。
测试用例:
String s("abc");
String s2(s);
String s3("lll");
s3 = s;
s3[0] = 'w';//写时拷贝
String s4("444");
String s5(s4);
s5 = s;