回顾默认拷贝构造函数
默认拷贝构造函数的策略就是逐个逐个成员依次拷贝。
示例代码:
#include<iostream>
using namespace std;
class Person
{
public:
Person(int id)
{
_id = id;
}
void display()
{
cout << _id << endl;
}
private:
int _id;
};
int main()
{
Person person1(20200501);
person1.display();
Person person2 = person1;
person2.display();
}
运行结果:
在Person person2 = person1时进行了依次拷贝。
一张图看懂浅拷贝和深拷贝
浅拷贝和深拷贝的区别就是拷贝的深度
这就是浅拷贝,只拷贝了成员数据的二进制数值。如上图中pName指针中存放的是指向某个堆空间的地址,拷贝后对象2的pName指针的值是相同的,也就是指向了同一个堆空间的地址。下面我们来看一下深拷贝的情况:
相比浅拷贝,深拷贝不是拷贝表面上pName的值,而是深入pName指向的堆区域把该堆区域的资源拷贝了一份。深拷贝是需要我们自己写拷贝构造函数的。
实例
浅拷贝和深拷贝的区别我们已经知道了,那么我们看看实际的代码。
浅拷贝
#include<iostream>
using namespace std;
class Person
{
public:
Person(const char * pN)
{
cout << "Constructing " << pN << endl;
pName = new char[strlen(pN) + 1];
if (pName != 0)
{
strcpy(pName, pN);
}
}
~Person()
{
cout << "Destructing " << pName << endl;
pName[0] = '\0';
delete pName;
}
protected:
char* pName;
};
int main()
{
Person p1("Randy");
Person p2 = p1;
}
程序刚开始运行时,创建p1对象,p1对象的构造函数从堆中分配空间并赋给数据成员pName,接着输出第一条语句。执行Person p2 = p1时,由于没有定义拷贝构造函数,于是就调用默认拷贝构造函数,将p1的值复制给p2,它们的成员pName指向同一个地址,并没有新分配堆空间给p2。这样就造成了资源将经历两次资源返还:当主函数结束时,对象逐个析构,析构p2时,输出第二条语句,将堆中字符串清成空串,然后将堆空间返还给系统。当析构p1时,因为这是pName指向的是空串,所以输出的第三条语句是"Destructing ";当执行"delete pName"时,系统就报错了,原因是分配的指针不存在了。
深拷贝
为了解决上述问题,必须要使用深拷贝。
#include<iostream>
#include<string.h>
using namespace std;
class Person
{
public:
Person(const char * pN)
{
cout << "Constructing " << pN << endl;
pName = new char[strlen(pN) + 1];
if (pName != 0)
{
strcpy(pName, pN);
}
}
Person(Person& p)
{
cout << "Copying " << p.pName << " into its own block\n";
pName = new char[strlen(p.pName)+1];
if (pName != 0)
strcpy(pName, p.pName);
}
~Person()
{
cout << "Destructing " << pName << endl;
pName[0] = '\0';
delete pName;
}
protected:
char* pName;
};
int main()
{
Person p1("Randy");
Person p2 = p1;
}
在代码中自行写了一个拷贝构造函数,在拷贝过程中用new语句分配了新的堆空间给新对象使用,因此解决了资源经历两次资源返还的问题。
总结,浅拷贝与深拷贝的不同是:前者只拷贝了成员的数值,而后者把使用的资源也拷贝了。
如果你的类需要析构函数来析构资源,则它也需要一个拷贝构造函数。