浅拷贝
也称位拷贝或值拷贝。如果对象中管理资源,编译器只是将对象中的值拷贝过来,就会导致多个资源共享一份资源,当一个对象销毁时就会将该资源释放,而这时另一些对象不知道该资源已经被释放,以为还有效,所以,对资源继续操作,就会发生访问违规的情况
比如在模拟实现string类时,构造函数中管理了资源,拷贝构造、赋值运算符重载用浅拷贝的方式会导致资源多次释放,也会导致内存泄漏
我们只是将s1的值放到s2中,这样就会导致s1、s2在底层共用同一块内存空间,当出了作用域要释放时,s3释放成功,当s1释放时,空间已经被s3释放了,s1的指向就成了野指针从而导致资源释放多次出现问题。没有重新开辟空间,对象之前的空间被覆盖了,也会引起内存泄漏。
图1
要解决同一份资源被多次释放的问题,引入深拷贝
深拷贝
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。
将其他对象的内容拷贝到另一个对象所在的空间中,而不是直接把值拷贝过来,这样每个对象就有一个自己的资源
图2
下面这个更方便些
//下面这个更方便
string(const string& s) :_str(nullptr)//如果不初始化为空,那最开始它里面是随机值,不是我们申请的,在释放时就会崩溃
{
string strTemp(s._str);//用s里的字符串构造临时对象
swap(_str, strTemp._str);//内容交换
//临时对象销毁,这时临时对象的之前内容已经被交换啦
}
string& operator=(string s)
{
swap(_str, s._str);
return *this;
}
写时拷贝
就是在浅拷贝的基础上增加了用计数的方式来实现
引用计数:用来记录资源使用者的个数,在构造时,将资源的计数给成1,没增加一个对象使用该资源,就给计数增加1,当被销毁时就减1,再检查是否需要释放资源;如果减之后对象是0,说明该对象是资源的最后一个使用者,将其释放;否则就不能释放,因为还有其他对象在使用该资源。
namespace bit
{
class string
{
public:
string(const char* str = "")
{
if (str == nullptr)
str = "";
_str = new char[strlen(str) + 1];
strcpy(_str, str);
_pCount = new int(1);
//_pCount++;
}
string(const string& s) :_str(s._str), _pCount(s._pCount)
{
++(*_pCount);
}
//给左操作数减1 释放资源s4
string& operator=(const string& s)
{
if (this != &s)
{
release();
_str = s._str;//共用空间
_pCount = s._pCount;//共用计数
++(*_pCount);
}
return *this;
}
~string()
{
release();
}
private:
void release()
{
if (_str && 0 == --(*_pCount))
{
delete[] _str;//指向其他地方,所以把资源释放
delete _pCount;
_str = nullptr;
_pCount = nullptr;
}
}
private:
char* _str;
int* _pCount;//按照指针的方式共享
};
}
void TestString()
{
bit::string s1("hello");
bit::string s2(s1);
bit::string s3("world");
bit::string s4(s3);
s1 = s3; //s123共用同一份空间 s4
s4 = s2;
}
int main()
{
TestString();
return 0;
}