C/C++基础之三(指针引起的内存泄露)

注意观察下面的代码,如果new出来的对象忘记被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
#include <iostream>

using namespace std;

class Person{
private:
int age;
public:
// 构造函数
Person(){
cout << "Person()" << endl;
}
void print_info(){
cout << "person_info" << endl;
}
//析构函数
~Person(){
cout << "~Person()" << endl;
}
};

void test_func(){
Person *p = new Person();
p->print_info();
// delete p;
}

int main(){
test_func();
}
那么如何让使用指针不必有那么大的心智负担了?因为局部变量使用之后,程序会自动帮我调用其析构函数,因此我们要将指针变量的创建修改修改:
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
//引入sp,其意为SmartPoint
class sp{
private:
Person *p;
public:
sp(){
p = NULL;
cout << "sp()" << endl;
}

sp(Person *other){
this->p = other;
cout << "sp(Person *other)" << endl;
}

~sp(){
if (p)
{
delete p;
}
cout << "~sp()" << endl;
}

// 对 -> 操作符进行重载
Person* operator->(){
return p;
}

};


void test_func(){
sp s = new Person();
s->print_info();
}

输出如下,可以看到我们并没有主动delete,程序就避免了内存泄漏。
1
2
3
4
5
Person()
sp(Person *other)
person_info
~Person()
~sp()
接下来我们继续改进:
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
    sp(Person *other){
this->p = other;
cout << "sp(Person *other)" << endl;
}
// const是不能少的,
sp(const sp &other){
this->p = other.p;
cout << " sp(sp *other)" << endl;
}

~sp(){
if (p)
{
delete p;
}
cout << "~sp()" << endl;
}

// 对 -> 操作符进行重载
Person* operator->(){
return p;
}

};


void test_func(sp& s1){
// 会调用拷贝构造函数:sp(const sp &other)
sp s = s1; // 这是初始化,而不是赋值 ,赋值是:sp s; s = s1; 赋值会调用operator= 操作符函数,初始化会调用拷贝构造函数
s->print_info();
}

int main(){

sp s = new Person();
for(int i=0;i<2;i++){
test_func(s);
}

}
输出如下,程序发生了崩溃,因为两次进行delete p;
1
2
3
4
5
6
7
8
9
Person()
sp(Person *other)
sp(const sp *other)
person_info
~Person()
~sp()
sp(const sp *other)
person_info
~Person()
没事,遇事不要慌,东西都在后备箱,看我们的破解大法
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
class Person{
private:
int age;
int count;
public:
// 构造函数
Person(){
cout << "Person()" << endl;
}
//增加引用计数
void incCount(){
this->count++;
}
//减少引用计数
void decCount(){
this->count--;
}
// 获取引用计数
int getCount(){
return this->count;
}
void print_info(){
cout << "person_info" << endl;
}
//析构函数
~Person(){
cout << "~Person()" << endl;
}
};

//引入sp,其意为SmartPoint
class sp{
private:
Person *p;
public:
sp(){
p = NULL;
cout << "sp()" << endl;
}

sp(Person *other){
this->p = other;
this->p->incCount();
cout << "sp(Person *other)" << endl;
}
// const是不能少的,
sp(const sp &other){
this->p = other.p;
this->p->incCount();
cout << " sp(const sp *other)" << endl;
}

~sp(){
if (p)
{
if (p->getCount()>1)
{ //存在多个引用
p->decCount();
}else{
delete p;
}
}
cout << "~sp()" << endl;
}

// 对 -> 操作符进行重载
Person* operator->(){
return p;
}

};

Person只new了一个,因此也只析构了一次,程序正确:

1
2
3
4
5
6
7
8
9
10
Person()
sp(Person *other)
sp(const sp *other)
person_info
~sp()
sp(const sp *other)
person_info
~sp()
~Person()
~sp()
程序似乎还不够优美,1. 引用计数写在Person里面的 2. 如果换一个类怎么办了?
  1. 引入RefBase基类用于计数
  2. 使用模板类实现任意对象的自定义只能指针

一切尽在不言中,上代码

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
93
94
95
96
97
98
99
100
101
102
103
104
class RefBase{
private:
int count; //构造对象时不初始化会得到一个无法想象的结果
public:

RefBase():count(0){
cout << "RefBase()" << endl;
}

void incCount(){
this->count++;
}
void decCount(){
this->count--;
}
int getCount(){
return this->count;
}

virtual ~RefBase(){
cout << "~RefBase" << endl;
}

};


class Person : public RefBase{
private:
int age;

public:
// 构造函数
Person(){
cout << "Person()" << endl;
}
void print_info(){
cout << "person_info" << endl;
}
//析构函数
~Person(){
cout << "~Person()" << endl;
}
};

//引入sp,其意为SmartPoint
template<typename T>
class sp{
private:
T *p;
public:
sp(){
p = NULL;
cout << "sp()" << endl;
}

sp(T *other){
this->p = other;
this->p->incCount();
cout << "sp(Person *other)" << endl;
}
// const是不能少的, 拷贝构造函数:sp s2 = s1; sp s3(s1); 这两种创建对象的方式都会调用拷贝构造函数; sp s4; s4 = s1; 这种写法只会调用 operator= 操作符函数
sp(const sp &other){
this->p = other.p; // 指针的直接拷贝
this->p->incCount();
cout << " sp(const sp *other)" << endl;
}

~sp(){
if (p)
{
cout << "value is :" << p->getCount() << endl;
if (p->getCount()>1)
{ //存在多个引用
p->decCount();
}else{
delete p;
}
}
cout << "~sp()" << endl;
}

// 对 -> 操作符进行重载,返回Person的指针
T* operator->(){
return p;
}

};

template<typename T>
void test_func(sp<T>& s1){
// 会调用拷贝构造函数:sp(const sp &other)
sp<T> s = s1; // 这是初始化,而不是赋值 ,赋值是:sp s; s = s1; 赋值会调用operator= 操作符函数,初始化会调用拷贝构造函数
s->print_info();
}

int main(){
//只new了一次,
sp<Person> s = new Person();
//test_func结束被释放两次???
for(int i=0;i<2;i++){
test_func(s);
}

}
输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
RefBase
Person()
sp(Person *other)
sp(const sp *other)
person_info
value is :2
~sp()
sp(const sp *other)
person_info
value is :2
~sp()
value is :1
~Person()
~RefBase
~sp()

那么到这里是否就结束了呢?No, 并没有,实际上这个代码是线程不安全的。Android源码中就有很好的例子,__sync_fetch_and_add就是确保线程安全。

1
2
3
4
5
6
7
8
9
inline LightRefBase() : mCount(0) { }
inline void incStrong(__attribute__((unused)) const void* id) const {
__sync_fetch_and_add(&mCount, 1);
}
inline void decStrong(__attribute__((unused)) const void* id) const {
if (__sync_fetch_and_sub(&mCount, 1) == 1) {
delete static_cast<const T*>(this);
}
}