C/C++拾遗之深拷贝和浅拷贝

深拷贝(Deep copy)和浅拷贝(Shallow copy)是指对于类或结构体这类复合类型的变量,当它们的成员变量中含有指针时,在赋值或初始化的过程中,如果只是修改指针的指向,则属于浅拷贝(也称位拷贝)。因为此时指针所指向的实际内容依然只有一份,当其中一个变量销毁时,则该内存将被释放,那么此时另一个变量中将存在悬挂指针。而如果在发生拷贝时首先将需要被拷贝的内容复制到一个新的内存地址,然后再将相应的指针指向该新地址,则称为深拷贝。SOF上的一个相关回答http://stackoverflow.com/questions/184710/what-is-the-difference-between-a-deep-copy-and-a-shallow-copy<>

首先提示:在C++中,对于类型class1来说,class1 a(b);等价于class1 a = b;都将调用拷贝构造函数进行初始化,而不是将后面的语句中使用赋值运算符对a进行赋值。如果要使用赋值运算符将b赋值给a,需要class1 a; a = b;

下面举例说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <iostream>
#include <cstring>

//注释语句注释的是紧接着下面的语句
class A {
public:
//指定为delete,阻止编译器合成默认的构造函数,当调用该构造函数时,将出现编译错误
//如果不指定为delete,或指定为default,则系统将合成默认的构造函数
A() = delete;

//将定义该构造函数为深拷贝构造函数,即需要将str的内容复制到一个新的内存地址中,
//而不仅仅是将成员变量指针name指向str所在的地址
A(const char *str);

//如果不定义该拷贝构造函数,则编译器会合成一个默认的拷贝构造函数
//如果不定义赋值运算符,则合成的赋值运算符将会调用该拷贝构造函数
A(const A &other);

//深拷贝的拷贝构造函数,为了与上面的构造函数区分,多加一个无用的形参
A(const A &other, bool unuse);

//定义为一个深拷贝的赋值运算符,也就是将other的成员变量name所指定的内存中的内容
//复制到一个新地址,并将赋值运算符左侧变量的name指向该新地址
A& operator=(const A& other);

~A();
private:
char *name;
bool unused;
};

//浅拷贝,因为当other析构时,other的成员变量name所指向的空间将被销毁
//被赋值的实例变量name将成为悬挂指针
A::A(const A &other)
{
std::cout << "浅拷贝的拷贝构造函数" << std::endl;
name = other.name;
}

//深拷贝的构造函数,使用新的内存来存放对象other的成员变量name所指向内存的内容
A::A(const A &other, bool unuse)
{
std::cout << "深拷贝的拷贝构造函数" << std::endl;
name = new char[strlen(other.name) + 1];
strcpy(name, other.name);
unused = unuse;
}


A::A(const char *str)
{
std::cout << "深拷贝的构造函数" << std::endl;
if (str != nullptr) {
name = new char[strlen(str) + 1];
std::strcpy(name, str);
}
else {
name = new char[1];
name[0] = '\0';
}
}


//深拷贝的赋值运算符
A& A::operator=(const A& other)
{
std::cout << "深拷贝的赋值运算符" << std::endl;
if (this != &other) {
A to(other, true); //调用深拷贝构造函数,以免浅拷贝导致内存泄漏
char *tn = name;
name = to.name;
to.name = tn;
}
return *this;
}

A::~A()
{
std::cout << "析构函数" << std::endl;
delete[] name;
}

int main(void)
{
char ts[] = "I love China";
A a(ts); //深拷贝,析构时没有问题
// A b(a); //浅拷贝的构造函数,a和b的name将指向同一块内存,所以时析构时将出错
// A c = a; //等价于A b(a)都是调用拷贝构造函数
A d(ts); //先定义d,再使用a赋值给d,将调用赋值运算符
d = a; //如果没有自定义赋值运行符,将调用合成的赋值运算符,析构d时将出错
return 0;
}

输出结果为:

1
2
3
4
5
6
7
深拷贝的构造函数
深拷贝的构造函数
深拷贝的赋值运算符
深拷贝的拷贝构造函数
析构函数
析构函数
析构函数

如果使用浅拷贝,也就是取消注释A c = a;输出结果为:

1
2
3
4
5
6
7
8
9
10
11
12
深拷贝的构造函数
浅拷贝的拷贝构造函数
深拷贝的构造函数
深拷贝的赋值运算符
深拷贝的拷贝构造函数
析构函数
析构函数
析构函数
析构函数
*** Error in `./copy_deep_shallow': double free or corruption (fasttop): 0x0000000002595010 ***

Command terminated

由此可见
深拷贝会拷贝动态分配的成员对象,而不仅仅是简单的指针赋值
深拷贝只拷贝非静态成员数据,因为类的静态成员被类及所有类对象共有一份