10. 深拷贝和浅拷贝
浅拷贝 是指对象之间复制时,对应的成员变量依次复制。对于指针类型的变量只进行值复制,指针指向同一地址,指向的内存会共用,数据会互相影响。C++ 对象默认是浅拷贝。
深拷贝 是指在对象复制时,对应的普通成员变量依次复制。对于指针类型的变量指向的内容也会复制,致使各个对象的数据各自独立,互不影响。C++ 需要通过拷贝构造函数(后面会讲)和拷贝赋值函数(后面会讲)类实现。
浅拷贝示例
// filename: shallow_copy.cpp
#include <iostream>
using namespace std;
class Rectangle {
public:
Rectangle(int w=1, int h=1):width(w), height(h) {
cout << "有参的构造函数被调用,w:" << w << " h:" << h << endl;
}
void showInfo() {
cout << "width:" << width << " height:" << height << endl;
}
private:
int width;
int height;
};
int main(int argc, char * argv[]) {
Rectangle r1(9, 8); // 调用有参数的构造函数
Rectangle r2(r1); // 浅拷贝(调用默认的拷贝构造)
Rectangle r3 = r1; // 浅拷贝(调用默认的拷贝构造)
Rectangle r4; // 调用有参数的构造函数
r4 = r1; // 浅拷贝(调用默认的拷贝赋值函数)
r1.showInfo();
r2.showInfo();
r3.showInfo();
r4.showInfo();
return 0;
}
编译和运行结果如下:
weimz@mzstudio:~$ g++ -o shallow_copy shallow_copy.cpp
weimz@mzstudio:~$ ./shallow_copy
有参的构造函数被调用,w:9 h:8
有参的构造函数被调用,w:1 h:1
width:9 height:8
width:9 height:8
width:9 height:8
width:9 height:8
可见 对象 r2、r3、r4的成员变量宽(width)和高(height)值都是从 r1 对象两个成员变量赋值而来。
上述创建四个 Rectangle 类型的对象,但这里却只调用了两次构造函数,因为 r2 和 r3 创建时调用的是默认的拷贝构造函数。
带有指针类型成员变量的对象的浅拷贝示例
// filename: shallow_copy2.cpp
#include <iostream>
#include <string.h>
using namespace std;
class Person {
public:
Person(const char * name, int a):age(a) {
if (NULL == name) { // 分配一个字节存放尾零
pname = (char*)malloc(1);
*pname = '\0';
} else {
pname = (char*)malloc(strlen(name)+1);
strcpy(pname, name);
}
cout << "构造函数被调用,动态分配了内存!" << endl;
}
~Person(void) {
// 释放内存
cout << "析构函数被调用,即将释放内存" << endl;
free(pname);
}
public:
void printInfo() {
cout << pname << "今年" << age << "岁\n";
}
private:
char * pname; // 姓名
int age; // 年龄
};
int main(int argc, char * argv[]) {
Person p1("老魏", 35);
Person p2 = p1;
p1.printInfo();
p2.printInfo();
return 0;
}
编译和运行结果如下:
weimz@mzstudio:~$ g++ -o shallow_copy2 shallow_copy2.cpp
weimz@mzstudio:~$ ./shallow_copy2
构造函数被调用,动态分配了内存!
老魏今年35岁
老魏今年35岁
析构函数被调用,即将释放内存
析构函数被调用,即将释放内存
free(): double free detected in tcache 2
已中止 (核心已转储) ./shallow_copy2
在上述程序结构中可以看出,我们一共创建了两个 Person 类型的对象 p1 和 p2。从构造函数的调用情况来看,创建 p1 对象时调用了一次构造函数并只动态分配了一次内存。在创建 p2 对象时实际是调用的是默认的拷贝构造函数将 p1 成员变量的值依次赋值给 p2 的各个成员变量,因此 p2.pname 指针也同样指向了 p1.pname 指向的动态分配的内存。即保存名字的内存是两个对象共有的内存,并没有进行复制。
两个对象销毁时都会调用析构函数,第一次调用析构函数时会释放这段共享内存,当第二次调用析构函数时则会再次释放已经释放过的内存,因此就会出现程序异常崩溃的情况。
从上述示例可知,在使用 p1 对象创建 p2 时,p2.pname 指针应当指向重新开辟的新内存,避免内存共用,因此需要使用深拷贝。
在 C++ 中,实现深拷贝的最好方法是实现拷贝构造和拷贝赋值这两个特殊的成员函数。
实验
尝试画图理解 带有指针类型成员变相的浅拷贝示例 中 p1 和 p2 两个对象成员变量的值,以及动态分配内存和释放的过程。