指针成员与拷贝构造
在 C++中,如果一个类中有一个指针成员,就要特别注意拷贝构造函数,一不小心就会出现内存泄露。一个经典的浅拷贝例子:
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
| #include <iostream>
using namespace std;
class HasPtrMem {
public:
HasPtrMem() : d(new int(0)) {
}
HasPtrMem(const HasPtrMem& h) : d(h.d) {
// 直接赋值,导致a.d和b.d指向同一位置
}
~HasPtrMem() {
delete d;
}
int* d;
};
int main() {
HasPtrMem a;
HasPtrMem b(a); // 调用隐式拷贝构造函数
cout << *a.d << endl;
cout << *b.d << endl; // delete悬空指针,出现异常
}
|
稍微改下实现深拷贝
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
| #include <iostream>
using namespace std;
class HasPtrMem {
public:
HasPtrMem() : d(new int(0)) {
}
HasPtrMem(const HasPtrMem& h) : d(new int(*h.d)) {
// new 一块内存,把分配来的指针交还给d,这样做能避免悬挂指针
}
~HasPtrMem() {
delete d;
}
int* d;
};
int main() {
HasPtrMem a;
HasPtrMem b(a);
cout << *a.d << endl;
cout << *b.d << endl;
}
|
移动语义
先看例子
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
| #include <iostream>
using namespace std;
class HasPtrMem {
public:
HasPtrMem() : d(new int(3)) {
cout << "Construct:" << ++n_cstr << endl;
}
// 拷贝构造函数
HasPtrMem( const HasPtrMem& h) : d(new int(*h.d)) {
cout << "Copy construct" << ++n_cptr << endl;
}
// 移动构造函数
HasPtrMem(HasPtrMem&& h) : d(h.d) {
h.d = nullptr; // 将临时值的指针成员赋值为空
cout << "Move construct : " << ++n_mvtr << endl;
}
~HasPtrMem() {
delete d;
cout << "Destruct:" << ++n_dstr << endl;
}
int* d;
static int n_cstr;
static int n_dstr;
static int n_cptr;
static int n_mvtr;
};
int HasPtrMem::n_cstr = 0;
int HasPtrMem::n_dstr = 0;
int HasPtrMem::n_cptr = 0;
int HasPtrMem::n_mvtr = 0;
HasPtrMem GetTemp() {
HasPtrMem h;
cout << "Resource from " << __func__ << ":" << hex << h.d << endl;
return h;
}
int main() {
HasPtrMem a = GetTemp();
cout << "Resource from " << __func__ << ":" << hex << a.d << endl;
cout << "*d = " << *a.d << endl;
}
|
1
2
3
4
5
6
7
| Construct:1
Resource from GetTemp:0x21d8d016130
Move construct : 1
Destruct:1
Resource from main:0x21d8d016130
*d = 3
Destruct:2
|
当调用 GetTemp()后,返回一个临时变量,把临时变量的值赋值给 a 时会调用移动构造函数,移动构造函数把 d 的值赋值给 a,然后把 h.d 赋值为 nullptr,这样 a.d 就指向原先 h.d 的堆内存,当调用 h 的析构函数时,并不会析构 h.d 原先指向的堆,因为 h.d 为空。这样 a 就不用重新分配堆内存。
注意:g++ 编译时要添加 -fno-elide-constructors
选项关闭构造函数优化才能看到上面的结果。