C++ 深拷贝和浅拷贝
浅拷贝: 简单的赋值拷贝操作
深拷贝: 在堆区重新申请空间,进行拷贝操作
以上两种都涉及到对象的复制;在平时,二者区别不大,但当对象成员包含有指针成员时,他们间的区别就显得尤为重要了
一、区别
1 在未定义拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝(不用自己构造),它能够完成成员的简单的值的拷贝一一复制。当数据成员中没有指针时,浅拷贝是可行的;但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址(同一个堆区),当对象快结束时,会调用两次析构函数(析构函数也无需自己构造,但想要知道析构函数的工作可以自己构造析构函数用输出来记录),而导致指针悬挂现象,所以,此时,必须采用深拷贝。
2 深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据(新的堆区空间进行拷贝),从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝。
浅拷贝
同一类型的对象之间可以赋值,使得两个对象的成员变量的值相同,两个对象仍然是独立的两个对象,这种情况被称为浅拷贝.
一般情况下,浅拷贝没有任何副作用,但是当类中有指针,并且指针指向动态分配的内存空间,析构函数做了动态内存释放的处理,这可能导致两个对象共享相同的资源,从而引发潜在的问题,如内存泄漏、意外修改共享资源等。
一般来说编译器默认帮我们实现的拷贝构造函数就是一种浅拷贝。
POD(plain old data) 类型的数据就适合浅拷贝
POD(Plain Old Data)通常用于描述简单的数据结构,这些结构不包含复杂的特性,比如继承、虚函数或自定义构造函数等。POD 类型的数据在 C 和 C++ 等语言中非常常见。
特点
- 简单结构:
- POD 结构通常只包含基本数据类型(如 int、char、float 等)和其他 POD 类型的成员。
- 没有用户定义的构造函数:
- POD 类型不应该有自定义的构造函数、析构函数或赋值运算符。
- 有效的内存布局:
- POD 类型的数据通常具有简单且连续的内存布局,使得它们可以安全地进行内存拷贝或通过指针操作。
深拷贝
深拷贝在浅拷贝的基础上,还复制了指针指向的内存
因此,两个对象不会共享相同的资源,避免了潜在问题。
深拷贝通常需要显式实现拷贝构造函数和赋值运算符重载。
当类中有指针,并且此指针有动态分配空间,析构函数做了释放处理,往往需要自定义拷贝构造函数,自行给指针动态分配空间,深拷贝。
示例代码:
#include<iostream>
#include<string>
using namespace std;
class person {
public:
person() {
cout << "默认空参构造函数" << endl;
}
person(int age,int height) {
m_age = age;
m_height = new int(height);
cout << "自定义有参构造函数" << endl;
}
~person() {
// 析构代码,用来手动释放堆区开辟的内存
if (m_height != NULL) {
delete m_height;
m_height = NULL;
}
cout << "默认析构函数" << endl;
}
// 自己实现拷贝构造函数,解决浅拷贝问题
person(const person& p) {
cout << "person 拷贝函数调用" << endl;
m_age = p.m_age;
m_height = p.m_height;// 编译器默认实现就是这行代码
// 深拷贝
m_height = new int(*p.m_height);// 新建堆区空间来存储数据
}
int m_age; // 年龄
int* m_height; // 身高
};
void test() {
person p1(18,160);
cout << "p1_age = " << p1.m_age << "\tp1.height = " << *p1.m_height << endl;
person p2(p1);
cout << "p2.age = " << p2.m_age << "\tp2.height = " << *p2.m_height << endl;
}
int main() {
test();
system("pause");
return 0;
}
这里,就像下图一样:
运行结果如图
总结
深拷贝和浅拷贝的区别是在对象状态中包含其它对象的引用的时候,深拷贝的实现需要构造拷贝函数新建一个堆区空间在进行拷贝,浅拷贝直接拷贝即可(简单的值的拷贝)。