浅拷贝
浅拷贝简介
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
一般情况下,只需使用系统提供的浅拷贝构造函数即可,但是,如果对象的数据成员包括指向堆空间的指针,就不能使用这种拷贝方式,因为两个对象都拥有同一个资源,对象析构时,该资源将经历两次资源返还,此时必须自定义深拷贝构造函数,为创建的对象分配堆空间,否则会出现动态分配的指针变量悬空的情况。
部分情况下浅拷贝所带来的问题
首先看下面这段代码可以正常运行。
#include<iostream>
#include<string.h>
#include<stdlib.h>
using namespace std;
class String
{
public:
String(const char *str = "")
{
m_data = (char*)malloc(strlen(str) + 1);
strcpy(m_data, str);
}
char* GetString()const
{
return m_data;
}
~String()
{
free(m_data);
m_data = NULL;
}
private:
char* m_data;
};
int main()
{
String s1("abc");
cout << s1.GetString() << endl;
}
此代码可以正常运行,下图为运行结果
然后我们在主函数中建立s2,并通过s1进行拷贝构造
int main()
{
String s1("abc");
cout << s1.GetString() << endl;
String s2 = s1;
}
在运行后发现程序崩溃
原因:
在实例化s1时程序正常执行,而当用s1拷贝构造s2时,相当于s1和s2指向了同一块空间,如下图:
打开监视窗口,如下图,我们看到s1和s2的m_data都指向了0x013b1e60的位置,也就是说同一块内存被两个指针所指。
程序运行到这里都没有问题,但是当主函数结束,调用析构函数时,先通过s2对象将m_data所指向的空间释放,再释放s1,而此时s1对象的m_data还指向刚刚已经被释放过的空间,也就是说这个空间被释放了两次,所以程序会崩溃。
通过这个例子我们会发现,默认的拷贝构造函数是无法满足我们所有的需求的,这时我们需要自己编写拷贝构造函数。
深拷贝
深拷贝简介
在进行赋值之前,为指针类型的数据成员另辟了一个独立的内存空间,实现真正内容上的拷贝。这种拷贝称为深拷贝。
深拷贝操作
我们希望在拷贝构造时不能简单地将s2中m_data的指针指向s1中m_data所指向的地址,而是要再申请一个跟它一样大小的空间,并且空间里的值也要相同,再让s2中的m_data指向新申请的空间。如下图:
编写拷贝构造函数代码如下:
String(const String &s)
{
m_data = (char*)malloc(strlen(s.m_data) + 1);//申请空间
strcpy(m_data, s.m_data);//将数据拷贝
}
在我们进行深拷贝后,程序正常执行,打开监视窗口,我们也可以看到两个对象的m_data所指向的空间并不一样,表明深拷贝操作成功。
总结:
如果一个类拥有指针类型的成员变量,那么绝大部分情况下就需要深拷贝,因为只有这样,才能将指针指向的内容再复制出一份来,让原有对象和新生对象相互独立,彼此之间不受影响。如果类的成员变量没有指针,一般浅拷贝足以。