浅拷贝、深拷贝与写时拷贝

时间:2022-12-06 19:51:02

浅拷贝、深拷贝与写时拷贝

浅拷贝

  • 在默认拷贝构造函数中,拷贝的策略是逐个成员依次拷贝。
  • 如果拷贝构造函数简单地制作了一个该对象的拷贝,而不对它的本身进行资源分配和复制,就得面临一个麻烦的局面。
  • 即,两个对象都拥有同一个资源。
  • 当对象析构时,该资源将经历两次资源返还。

    浅拷贝、深拷贝与写时拷贝

  • 创建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;
};
}