浅拷贝:利用类提供的默认拷贝构造函数,将一个对象的成员所在内存的数据复制给另一个对象的成员。
对于简单的类来说,使用浅拷贝就可以满足正常需求了,但是当类中有指向动态内存的指针时,浅拷贝的使用很容易造成内存错误,就需要显式定义拷贝构造函数。
浅拷贝错误案例
一起看一个浅拷贝的案例:
#include<iostream>
using namespace std;
#include<memory>
class Base
{
public:
Base(int A, int B)
{
m_A = A;
m_B = new int(B);
}
~Base()
{
delete m_B;
m_B = NULL;
}
public:
int m_A;
int* m_B;
};
int main()
{
Base b(10, 20);
cout << "m_B的值为:" << b.m_B << " m_B所指向的值为:" << *(b.m_B) << endl;
Base c(b); //默认拷贝构造函数
cout << "m_B的值为:" << c.m_B << " m_B所指向的值为:" << *(c.m_B) << endl;
system("pause");
return 0;
}
运行结果如下:
结果看起来好像没啥问题,但是当我按任意键结束主程序时,发现程序崩了,如下:
那么这是什么原因呢?我们先看一下,对象b和c的内存关系如下:
可以看到,对象b和c中的m_B储存的都是形参B的地址,而B的值为20。也就是说对象c拷贝对象b的时候,c.m_B只是拷贝了b.m_B的值,即B的地址,所以c.m_B和b.m_B都是指向变量B的指针。
默认的拷贝构造函数可写为:
//浅拷贝
Base(const Base& _Base)
{
this->m_A = _Base.m_A;
this->m_B = _Base.m_B;;
}
在主程序结束的时候,会先销毁对象b,此时b.m_B指向的B的内存资源也会被释放,那么,程序继续销毁对象c时,指针c.m_B也要释放B的内存资源,就会造成重复释放内存,从而出现错误。
深拷贝
针对浅拷贝的问题,就需要通过深拷贝来解决。所谓深拷贝,就是显式定义类的拷贝构造函数,不仅会将原对象的成员变量复制给新对象,还会在堆中为新对象分配一块新的内存,并将原对象持有的动态内存资源也拷贝过来。
那么,看一下用深拷贝重新实现上面的代码:
#include<iostream>
using namespace std;
#include<memory>
class Base
{
public:
Base(int A, int B)
{
m_A = A;
m_B = new int(B);
}
//深拷贝
Base(const Base& _Base)
{
this->m_A = _Base.m_A;
int temp = *(_Base.m_B);
this->m_B = new int(temp); //在堆中开辟新内存存储变量temp
}
~Base()
{
delete m_B;
m_B = NULL;
}
public:
int m_A;
int* m_B;
};
int main()
{
Base b(10, 20);
cout << "m_B的值为:" << b.m_B << " m_B所指向的值为:" << *(b.m_B) << endl;
Base c(b); //默认拷贝构造函数
cout << "m_B的值为:" << c.m_B << " m_B所指向的值为:" << *(c.m_B) << endl;
system("pause");
return 0;
}
运行结果:
可以看到,运行结果与浅拷贝的结果一样,但是在主程序结束时并不会出现内存错误。可以看一下深拷贝的内存关系,如下图:
可以看到,b.m_B和c.m_B的值是不一样的,即存储的地址不一样。也就是说,在深拷贝的过程中,对象c在堆区中重新开辟了一块内存空间,存储着与b中动态内存相同的资源,且指针c.m_B指向这块新的资源。因此,在主程序结束的时候,销毁对象b时就释放其对应的内存资源,销毁对象c时就释放其对应的内存资源,不会发生内存错误。