C++深拷贝与浅拷贝的区别-简单易懂
介绍
浅拷贝就比如像引用类型,而深拷贝就比如值类型。
浅拷贝是指源对象与拷贝对象共用一份实体,仅仅是引用的变量不同(名称不同)。对其中任何一个对象的改动都会影响另外一个对象。举个例子,一个人一开始叫张三,后来改名叫李四了,可是还是同一个人,不管是张三缺胳膊少腿还是李四缺胳膊少腿,都是这个人倒霉。
深拷贝是指 源对象与拷贝对象互相独立 ,其中任何一个对象的改动都不会对另外一个对象造成影响。举个例子,一个人名叫张三,后来用他克隆(假设法律允许)了另外一个人,叫李四,不管是张三缺胳膊少腿还是李四缺胳膊少腿都不会影响另外一个人。比较典型的就是Value(值)对象,如预定义类型Int32,Double,以及结构(struct),枚举(Enum)等。
浅拷贝
浅拷贝就是对象的数据成员之间的简单赋值, 如你设计了一个没有类而没有提供它的复制构造函数,当用该类的一个对象去给令一个对象赋值时所执行的过程就是浅拷贝,如:
#include <iostream>
using namespace std;
class A
{
public:
A(int _data) : data(_data){}
int GetX() {return data;}
A() {}
private:
int data;
};
int main()
{
A a(5);
A b;
b = a;
cout << b.GetX() << endl;
return 0;
}
运行结果为 5. 这就是一个浅拷贝的过程。
如果对象中没有其他的资源(如:堆,文件,系统资源等),则深拷贝和浅拷贝没有什么区别,
但当对象中有这些资源时,例子:
#include <iostream>
using namespace std;
class A
{
public:
A() {}
A(int _size) : size(_size)
{
data = new int[size];
*data = 5;
}
~A()
{
delete[] data;
data = nullptr;
}
int Get_Val() {return *data;}
int *Get_Val_add() { return data; }
private:
int *data;
int size;
};
int main()
{
A a(5);
A b(a);
cout << b.Get_Val() << endl;
return 0;
}
因为类A中的复制构造函数是编译器生成的,所以A b(a)执行一个浅拷贝过程,浅拷贝只是对象数据之间的简单赋值 (如:b.size = a.size, b.data = a.data)
这里b的指针data 和a的指针指向了堆上的同一块内存,a和b析构时,b先把其data指向的动态分配的内存释放了一次,而后a析构时又将这块已经释放过的内存再释放一次。
对同一块内存释放执行2次及2次以上的释放会造成内存泄露或者是程序crash!
深拷贝
需要用 深拷贝 来解决上述问题:
深拷贝就是当拷贝对象中有对其他资源(如堆、文件、系统等)的引用时(引用可以是指针或引用)时,对象的另开辟一块新的资源,而不再对拷贝对象中有对其他资源的引用的指针或引用进行单纯的赋值。
#include <iostream>
using namespace std;
class A
{
public:
A() {}
A(int _size) : size(_size)
{
data = new int[size];
*data = 5;
}
A( const A& _A) : size(_A.size) //深拷贝
{
data = new int[size];
*data = *_A.data;
}
~A()
{
delete[] data;
data = nullptr;
}
int Get_Val() {return *data;}
int *Get_Val_add() { return data; }
private:
int *data;
int size;
};
int main()
{
A a(5);
A b(a);
cout << b.Get_Val() << endl;
return 0;
}
总结
深拷贝和浅拷贝的区别是在对象状态中包含其它对象的引用的时候,当拷贝一个对象时,如果需要拷贝这个对象引用的对象,则是深拷贝,否则是浅拷贝。
拷贝构造函数经常用在一个对象对另一个对象初始化的情况,如果使用默认的拷贝构造函数,用一个对象初始化另外一个对象的,C++会进行简单的成员变量之间的赋值,假如对象有申请内存的行为,被初始化的对象的内存地址会被赋值为初始化对象的内存地址,这样一来,两个对象拥有相同的内存地址,一旦其中有一个对象析构的时候,释放了内存。另外一个对象析构的时候,会释放已经释放了的内存,导致了内存的多次释放,出现段错误。
还有一种情况是,一个对象直接给另外一个对象使用“=”进行赋值操作,对于这种情况C++也会采取简单的成员变量直接的赋值操作,这样会导致两个问题:
-
跟上面的问题一样,对象被析构的时候会导致同一内存的多次释放,引发段错误;
-
被赋值对象之前申请的内存地址被赋值对象的内存地址替换,没有得到释放,引发内存泄漏。
深拷贝与浅拷贝可以这样理解:如果一个类拥有内存资源,当这个类的对象发生复制过程的时候,内存资源重新分配,这个过程就是深拷贝,反之,没有重新分配内存资源,而是对象间的内存地址复制,就是浅拷贝。
解决调用拷贝构造函数导致的浅拷贝问题的方法是自定义拷贝构造函数,在拷贝构造函数中不进行内存地址的直接赋值,而是重新为对象分配内存空间。而解决“=”赋值操作引发的浅拷贝问题的方法是采用运算符重载解决。
参考: