C++ 中的深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是指在对象复制时,对象成员的复制方式不同。理解这两种拷贝构造方式非常重要,尤其是在涉及到动态内存分配和资源管理时。
浅拷贝(Shallow Copy)
浅拷贝是指在拷贝对象时,只复制对象的值,但对于指针类型的成员变量,拷贝的是指针的地址,即两个对象的指针成员指向同一块内存区域。
浅拷贝的特点:
- 复制对象的值,包括原始数据的成员。
- 对于指针成员,只复制指针的值(地址),导致两个对象的指针指向同一块内存。
- 这种方式可能会导致资源的共享和管理上的问题,尤其是在对象销毁时,如果多个对象指向同一块内存,可能会发生“双重释放”错误。
浅拷贝示例:
#include <iostream>
using namespace std;
class Student {
public:
int num;
int *age; // 指针成员,假设它指向动态分配的内存
// 构造函数
Student(int n, int a) {
num = n;
age = new int; // 动态分配内存
*age = a;
}
// 浅拷贝构造函数
Student(const Student &other) {
num = other.num;
age = other.age; // 只是拷贝指针的值(地址),没有进行深拷贝
}
// 析构函数
~Student() {
delete age; // 释放内存
}
};
int main() {
Student s1(1001, 20);
Student s2 = s1; // 调用浅拷贝构造函数
cout << "s1: " << s1.num << ", " << *s1.age << endl;
cout << "s2: " << s2.num << ", " << *s2.age << endl;
// s1和s2的age指针指向同一块内存区域
// 修改s1的age的值会影响s2的age,因为它们共享同一内存
*s1.age = 25;
cout << "After modifying s1.age:" << endl;
cout << "s1: " << s1.num << ", " << *s1.age << endl;
cout << "s2: " << s2.num << ", " << *s2.age << endl;
// 程序结束时,s1和s2的析构函数都会被调用,导致删除同一块内存
}
结果:
s1: 1001, 20
s2: 1001, 20
After modifying s1.age:
s1: 1001, 25
s2: 1001, 25
问题:
s1
和s2
的age
指针指向同一块内存,因此修改一个对象的age
会影响另一个对象。- 当程序结束时,两个对象都调用析构函数,释放同一块内存(发生双重释放错误)。
深拷贝(Deep Copy)
深拷贝是指在拷贝对象时,除了复制值之外,还会对指针类型成员指向的内存进行复制。每个对象会有自己独立的内存,不会出现共享资源的问题。
深拷贝的特点:
- 对象成员的所有值都会被复制。
- 对于指针成员,会重新分配内存并复制数据,确保每个对象都有自己的内存。
- 不会发生资源共享问题,因此避免了“双重释放”的错误。
深拷贝示例:
#include <iostream>
using namespace std;
class Student {
public:
int num;
int *age; // 指针成员
// 构造函数
Student(int n, int a) {
num = n;
age = new int; // 动态分配内存
*age = a;
}
// 深拷贝构造函数
Student(const Student &other) {
num = other.num;
age = new int; // 为指针成员重新分配内存
*age = *(other.age); // 复制数据,而不是指针地址
}
// 析构函数
~Student() {
delete age; // 释放内存
}
};
int main() {
Student s1(1001, 20);
Student s2 = s1; // 调用深拷贝构造函数
cout << "s1: " << s1.num << ", " << *s1.age << endl;
cout << "s2: " << s2.num << ", " << *s2.age << endl;
// s1和s2的age指针指向不同的内存区域
// 修改s1的age的值不会影响s2
*s1.age = 25;
cout << "After modifying s1.age:" << endl;
cout << "s1: " << s1.num << ", " << *s1.age << endl;
cout << "s2: " << s2.num << ", " << *s2.age << endl;
}
结果:
s1: 1001, 20
s2: 1001, 20
After modifying s1.age:
s1: 1001, 25
s2: 1001, 20
解释:
s1
和s2
的age
指针指向不同的内存区域,因此修改s1
的age
不会影响s2
。- 由于使用深拷贝,
s1
和s2
在析构时会分别释放各自的内存,不会发生双重释放问题。
总结:
- 浅拷贝:只复制对象的值,对于指针成员,复制的是指针的地址,导致多个对象共享相同的内存。
- 深拷贝:复制对象的值,对于指针成员,重新分配内存并复制数据,确保每个对象有独立的内存,避免了资源共享的问题。
自定义拷贝构造函数
为了避免浅拷贝带来的问题,特别是当对象中有动态分配的内存时,通常需要自己定义深拷贝构造函数。同时,还需要实现赋值操作符重载,以确保在对象赋值时,拷贝也是深拷贝。
class Student {
public:
int num;
int *age;
Student(int n, int a) {
num = n;
age = new int;
*age = a;
}
// 深拷贝构造函数
Student(const Student &other) {
num = other.num;
age = new int;
*age = *(other.age);
}
// 赋值操作符重载(深拷贝)
Student& operator=(const Student &other) {
if (this != &other) { // 防止自赋值
num = other.num;
delete age; // 释放旧的内存
age = new int; // 分配新内存
*age = *(other.age);
}
return *this;
}
~Student() {
delete age; // 释放内存
}
};
这样,无论是通过拷贝构造函数还是赋值操作符,都能确保每个对象都拥有自己独立的内存,避免了资源管理的冲突和双重释放问题。