书接上文!
C++中的指针

内存泄露问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MyObject
{
public:
MyObject()
{
cout<<"Create MyObject Object"<<'\n';
}

~MyObject()
{
cout<<"Delete MyObject Object"<<'\n';
}
};

int main()
{
MyObject* obj = new MyObject();
//delete obj;
return 0;
}

输出:

1
Create MyObject Object

智能指针

智能指针帮助我们自动释放不再使用的内存,从而避免内存泄漏问题

  • C++98中加入了auto_ptr
  • C++11中auto_ptr更新为unique_ptr,同时加入了shared_ptr、weak_ptr

智能指针本质上是对指针的包装。在构造智能指针类时创建一个指针,重载*、->等运算符,在析构智能指针类时释放该指针的地址。
下面是智能指针的简单实现:

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
template<class T>
class MySmartPtr
{
private:
T* ptr;
public:
MySmartPtr(T* p = nullptr)
{
ptr = p;
}

~MySmartPtr()
{
delete ptr;
}
};

class MyObject
{
public:
MyObject()
{
cout<<"Construct MyObject"<<endl;
}

~MyObject()
{
cout<<"Delete MyObject"<<endl;
}
};

int main()
{
MySmartPtr<MyObject> obj(new MyObject());
return 0;
}

智能指针的声明

各种智能指针都是一个模板类。可以使用如下方法创建(以auto_ptr为例):

1
2
3
auto_ptr<Class> valueName(new Class());
auto_ptr<vector<int>> valueName(new vector<int>());
auto_ptr<int> valueName(new int[3]);

auto_ptr

C++98的智能指针,已被unique_ptr替换掉,基本不用,就不介绍了。
有兴趣可以翻看这些资料:
C++智能指针:auto_ptr详解
std::auto_ptr CPP Reference

unique_ptr

auto_ptr的进阶版。
unique_ptr不共享指针,只能指向一个对象。无法复制到其他unique_ptr,无法进行值传递,也无法使用需要副本的任何STL算法。
unique_ptr的指针变动只能通过移动unique_ptr实现所有权的转换。

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
#include <iostream>
using namespace std;

class MyObject
{
public:
int num;

MyObject()
{
num = 0;
cout<<"Construct MyObject"<<endl;
}

MyObject(int n)
{
num = n;
cout<<"Construct MyObject, n="<<n<<endl;
}

void DebugInfo()
{
cout<<"DebugInfo: num = "<<num<<endl;
}

~MyObject()
{
cout<<"Delete MyObject, n="<<num<<endl;
}
};

void UniquePtrAdd(unique_ptr<MyObject> obj)
{
obj->num += 2;
}

void UniquePtrAddReference(unique_ptr<MyObject>& obj)
{
obj->num += 2;
}

int main()
{
unique_ptr<MyObject> ptr1 = std::make_unique<MyObject>();
unique_ptr<MyObject> ptr2 = std::make_unique<MyObject>(4);
//unique_ptr<MyObject> ptr3 = ptr1; ——Error
//unique_ptr<MyObject> ptr3(ptr2); ——Error
//UniquePtrAdd(ptr2); ——Error
ptr2->DebugInfo();
UniquePtrAddReference(ptr2);
ptr2->DebugInfo();

cout<<"ptr1: "<<ptr1<<endl;
unique_ptr<MyObject> ptr3 = std::move(ptr1);
cout<<"ptr1: "<<ptr1<<endl;
cout<<"ptr3: "<<ptr3<<endl;
ptr3->DebugInfo();

return 0;
}

输出:

1
2
3
4
5
6
7
8
9
10
Construct MyObject
Construct MyObject, n=4
DebugInfo: num = 4
DebugInfo: num = 6
ptr1: 00000238CFBF7A10
ptr1: 0000000000000000
ptr3: 00000238CFBF7A10
DebugInfo: num = 0
Delete MyObject, n=0
Delete MyObject, n=6

可以看到,经过转移的指针已经变为空指针,而转移目标获取了被转移指针指向的地址。

shared_ptr

多个指针共享一个对象。通过引用计数器检测指针使用状态。当没有指针指向该对象时该对象会自动销毁。
需要注意的是,**对于共享指针来说,传值传参和引用传参均可以改变对象的成员。**因为传值传参调用复制构造函数后用声明了一个指针,使用该指针进行的操作均会作用于所有指针指向的共同对象上。

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
void SharedPtrAdd(shared_ptr<MyObject> obj)
{
obj->num += 2;
cout<<"SharedPtr Count:"<<obj.use_count()<<endl;
}

void SharedPtrAddReference(shared_ptr<MyObject>& obj)
{
obj->num += 3;
}


int main()
{
shared_ptr<MyObject> ptr1 = std::make_shared<MyObject>(4);
cout<<"SharedPtr 1 Count:"<<ptr1.use_count()<<endl;
shared_ptr<MyObject> ptr2 = ptr1;
cout<<"SharedPtr 1 Count:"<<ptr1.use_count()<<endl;
shared_ptr<MyObject> ptr3 = ptr2;
cout<<"SharedPtr 3 Count:"<<ptr3.use_count()<<endl<<endl;

cout<<"Before Move All Pointer"<<endl;
ptr1 = std::make_shared<MyObject>(0);
ptr2 = ptr1;
ptr3 = ptr1;
cout<<"After Move All Pointer"<<endl<<endl;

SharedPtrAdd(ptr2); //—— shared_ptr在传值传参时需要注意对复制的指针进行操作,会影响到输入值。
SharedPtrAddReference(ptr1);
cout<<"SharedPtr Count:"<<ptr3.use_count()<<endl;
ptr3->DebugInfo();

return 0;
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Construct MyObject, n=4
SharedPtr 1 Count:1
SharedPtr 1 Count:2
SharedPtr 3 Count:3

Before Move All Pointer
Construct MyObject, n=0
Delete MyObject, n=4
After Move All Pointer

SharedPtr Count:4
SharedPtr Count:3
DebugInfo: num = 5
Delete MyObject, n=5

weak_ptr

weak_ptr用于辅助shared_ptr工作,不具有普通指针的功能。没有重载*和->。
weak_ptr只能从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr不共享资源,它的构造不会引起指针引用计数的增加。
weak_ptr可以用于避免循环引用带来的问题。

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>
#include "Course.h"
#include "Student.h"

using namespace std;

int main()
{
Course* c = new Course();
shared_ptr<Student> p1(new Student());
//weak_ptr<Student> p2(new Student()); ——Error

cout<<"p1 Count:"<<p1.use_count()<<endl;
weak_ptr<Student> p_weak(p1);
cout<<"p1 Count:"<<p1.use_count()<<endl;
cout<<"p_weak Count:"<<p_weak.use_count()<<endl;
shared_ptr<Student> p3(p1);
cout<<"p_weak Count:"<<p_weak.use_count()<<endl;

cout<<"p1 Course:"<<p1->course<<endl;
if(!p_weak.expired()) //expired() <==> (use_count() == 0)
{
shared_ptr<Student> p4= p_weak.lock(); //从弱指针获取共享指针的副本

p4->SetCourse(c);
cout<<"p1 Course:"<<p1->course<<endl;
}

shared_ptr<Student> p4 = make_shared<Student>();
p3 = p4;
p1 = p4;
cout<<"p_weak Count:"<<p_weak.use_count()<<endl;
return 0;
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
Create Course
Create Student
p1 Count:1
p1 Count:1
p_weak Count:1
p_weak Count:2
p1 Course:0000000000000000
p1 Course:000001DF98BCAB10
Create Student
Delete Student
p_weak Count:0 //弱指针在技术为0时不会自动销毁,通过lock()获取的共享指针副本为空
Delete Student

循环引用

当两个类分别有对方类型的一个实例,在释放指针时,就会出现释放一个指针而另一指针的为空的情况。

  • 使用普通指针的错误写法
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
// in Course.h
#pragma once
#include <iostream>

class Student;

class Course
{
public:
Student* student;
Course();
~Course();

void SetStudent(Student*& s);
};

// in Student.h
#pragma once
#include <iostream>
#include "Course.h"

class Student
{
public:
Course* course;
Student();
~Student();

void SetCourse(Course*& c);
};

// in main.cpp
#include <iostream>
#include "Course.h"
#include "Student.h"

using namespace std;

int main()
{
Course* c = new Course();
Student* s = new Student();

c->SetStudent(s);
s->SetCourse(c);

delete c;
cout<<"Student Has Course: "<<s->course->student<<endl;
return 0;
}

输出:

1
2
3
4
Create Course
Create Student
Delete Course
Student Has Course: DDDDDDDDDDDDDDDD

可以看到退出时学生对象中的课程指针已经被释放掉了。如果实际应用中试图在删除某一对象后通过另一对象进行访问就会发生错误。

  • 使用shared_ptr的错误用法
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
#pragma once
#include <iostream>

class Student;

class Course
{
public:
std::shared_ptr<Student> student; //修改
Course();
~Course();

void SetStudent(std::shared_ptr<Student>& s);
};

//in Student.h
#pragma once
#include <iostream>
#include "Course.h"

class Student
{
public:
std::shared_ptr<Course> course; //修改
Student();
~Student();

void SetCourse(std::shared_ptr<Course>& c);
};

// in main.cpp
#include <iostream>
#include "Course.h"
#include "Student.h"

using namespace std;

int main()
{
shared_ptr<Course> c(new Course());
shared_ptr<Student> s(new Student());

cout<<"Course Ptr:"<<c.use_count()<<endl;
cout<<"Student Ptr:"<<s.use_count()<<endl;

c->SetStudent(s);
s->SetCourse(c);

cout<<"Course Ptr:"<<c.use_count()<<endl;
cout<<"Student Ptr:"<<s.use_count()<<endl;
}

输出:

1
2
3
4
5
6
Create Course
Create Student
Course Ptr:1
Student Ptr:1
Course Ptr:2
Student Ptr:2
  • 使用weak_ptr的正确用法
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
//in Course.h
#pragma once
#include <iostream>

class Student;

class Course
{
public:
std::weak_ptr<Student> student; //修改
Course();
~Course();

void SetStudent(std::shared_ptr<Student>& s);
};

//in Student.h
#pragma once
#include <iostream>
#include "Course.h"

class Student
{
public:
std::shared_ptr<Course> course; //修改
Student();
~Student();

void SetCourse(std::shared_ptr<Course>& c);
};

// in main.cpp
#include <iostream>
#include "Course.h"
#include "Student.h"

using namespace std;

int main()
{
shared_ptr<Course> c(new Course());
shared_ptr<Student> s(new Student());

cout<<"Course Ptr:"<<c.use_count()<<endl;
cout<<"Student Ptr:"<<s.use_count()<<endl;

c->SetStudent(s);
s->SetCourse(c);

cout<<"Course Ptr:"<<c.use_count()<<endl;
cout<<"Student Ptr:"<<s.use_count()<<endl;
}

输出:

1
2
3
4
5
6
7
8
Create Course
Create Student
Course Ptr:1
Student Ptr:1
Course Ptr:2
Student Ptr:1
Delete Student
Delete Course

参考资料