指针成员与拷贝构造

在 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
34
#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
34
35
36
#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
55
#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;
}

当调用 GetTemp()后,返回一个临时变量,把临时变量的值赋值给 a 时会调用移动构造函数, 移动构造函数把 d 的值赋值给 a,然后把 h.d 赋值为 nullptr,这样 a.d 就指向原先 h.d 的堆内存, 当调用 h 的析构函数时,并不会析构 h.d 原先指向的堆,因为 h.d 为空。这样 a 就不用重新分配堆内存。