浅拷贝、深拷贝与写时拷贝
浅拷贝
- 在默认拷贝构造函数中,拷贝的策略是逐个成员依次拷贝。
- 如果拷贝构造函数简单地制作了一个该对象的拷贝,而不对它的本身进行资源分配和复制,就得面临一个麻烦的局面。
- 即,两个对象都拥有同一个资源。
- 当对象析构时,该资源将经历两次资源返还。
- 创建p2时,对象p1被复制给了p2,但资源并未复制,因此,p1和p2指向同一个资源。
- 这便被称为,浅拷贝。
深拷贝
- 当一个对象创建时,分配了资源,这时,就需要定义自己的拷贝构造函数,使之不但拷贝成员,也分配和拷贝资源。
- 创建p2时,对象p1被复制给了p2,同时资源也作了复制。
- 因此,p1和p2指向不同的资源。
-
这,便被称为深拷贝。
如果你的类需要析构函数来析构资源,则它也需要一个拷贝构造函数。
因为对象通常是自动被析构的。
如果需要一个自定义的析构函数,那就意味着有额外资源要在对象被析构之前释放。
此时,对象的拷贝就不是浅拷贝了。 深拷贝代码实现
#include<iostream>
#include<string.h>
#define _CRT_SECURE_NO_WARNINGS
#pragma warning( disable : 4996)
using namespace std;
传统写法
class String
{
public:
String(char* str = "")
:_str(new char[strlen(str)+1])
{
strcpy(_str, str);
}
String(const String& s)
{
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
String& operator=(const String& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
return *this;
}
~String()
{
if (_str)
{
delete[] _str;
}
}
char* GetStr()
{
return _str;
}
private:
char* _str;
};
//现代写法
class String
{
public:
String(char* str)
:_str(new char[strlen(str) + 1])
{
strcpy(_str, str);
}
String(const String& s)
:_str(NULL)
{
String tmp(s._str);
swap(_str, tmp._str);
}
String& operator=(String s)
{
swap(_str, s._str);
return *this;
}
~String()
{
if (_str)
{
delete[] _str;
}
}
char* GetStr()
{
return _str;
}
private:
char* _str;
};
写时拷贝
- 基于浅拷贝和深拷贝延伸出的写时拷贝具有更好的实用性。
- 在只需要进行 只读 操作时,执行浅拷贝。
- 而在需要进行 写 操作时,则执行深拷贝。
- 写时拷贝有两种方案可以实现,可根据自身需要进行取舍。
方案一
- 增加一个类成员 _refCountPtr,用来表示同一个堆有几个对象指向。
- 以便调用析构函数,防止空间被多次释放,或者部分空间未释放。
namespace COW1
{
class String
{
public:
String(const char*str) //构造函数
:_refCountPtr(new int(1))
{
_size = strlen(str);
_capacity = _size;
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
String(const String& s) //拷贝构造函数
:_str(s._str)
, _size(s._size)
, _capacity(s._capacity)
, _refCountPtr(s._refCountPtr)
{
(*_refCountPtr)++;
}
~String() //析构函数
{
Release();
}
//s1 = s2
String& operator=(const String& s) //运算符的重载
{
if (_str != s._str)
{
Release();
_str = s._str;
_refCountPtr = s._refCountPtr;
(*_refCountPtr)++;
}
return *this;
}
void Release()
{
if (--(*_refCountPtr) == 0)
{
cout << "Release!" << endl;
delete[] _str;
delete _refCountPtr;
}
}
void CopyOnWrite() //写时拷贝
{
if (*_refCountPtr > 1)
{
char* NewStr = new char[_capacity + 1];
strcpy(NewStr, _str);
(*_refCountPtr)--;
_str = NewStr;
_refCountPtr = new int(1);
}
}
char& operator[](size_t pos)
{
CopyOnWrite();
return _str[pos];
}
char operator[](size_t pos) const
{
return _str[pos];
}
const char* c_str()
{
return _str;
}
private:
char* _str;
int* _refCountPtr;
size_t _size;
size_t _capacity;
};
}
方案二
- 使用引用计数,将引用计数放在字符串的头四个字节中。
- 使得,每个对象都能有不同的引用计数。
namespace COW2
{
class String
{
public:
String(const char* str)
:_str(new char[strlen(str)+5])
{
strcpy(_str+4, str);
_str += 4;
GetRefCount() = 1;
}
String(const String& s)
:_str(s._str)
{
GetRefCount()++;
}
~String()
{
Release();
}
void Release()
{
if (--GetRefCount() == 0)
{
delete[] (_str-4);
}
}
void CopyOnWrite()
{
if (GetRefCount() > 1)
{
char* NewStr = new char[_capacity + 1];
strcpy(NewStr, _str);
GetRefCount()--;
_str = NewStr;
GetRefCount() = 1;
}
}
String& operator = (const String& s)
{
if (_str != s._str)
{
Release();
_str = s._str;
GetRefCount()++;
}
return *this;
}
char& operator[](size_t pos)
{
CopyOnWrite();
return _str[pos];
}
char operator[](size_t pos)const
{
return _str[pos];
}
int& GetRefCount()
{
return *((int*)(_str - 4));
}
const char* c_str()
{
return _str;
}
const char* GetStr()
{
return _str;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
}