上一篇笔记提到C++的智能指针,本节重点写一下智能指针的应用场景和使用中的坑
智能指针的背景
C++中比较头疼的是内存泄露问题,如果使用new动态申请内存,需要时刻记得delete回收内存,避免发生内存泄露。
对于分支很多的代码来讲,在多个分支进行内存释放很容易产生遗漏,排查代码非常浪费时间。
为了避免这种情况,C++采用智能指针的方式进行内存管理,智能指针本身就是一个类,在类的对象超出作用域范围时,会自动调用析构函数。
auto_ptr的坑
C++11之前,只有auto_ptr,不过auto_ptr存在很多缺点,如:
auto_ptr只能唯一引用内存地址,如果使用拷贝构造函数,会造成所属权的转移,导致以前的指针无效,此时如果再使用以前的指针会导致非法访问
如:
1 #include <iostream>
2 #include <memory>
3
4 using namespace std;
5
6 class A
7 {
8 public:
9 A(int num) { _num = num; }
10 ~A() { cout << "distroy A" << endl; }
11 int get() { return _num; }
12 void set(int num) { _num = num; }
13 void show() { cout << "num:" << _num << endl; }
14 private:
15 int _num;
16 };
17
18 int main()
19 {
20 cout << "begin" << endl;
21 {
22 auto_ptr<A> ptr_a(new A(10));
23 auto_ptr<A> ptr_a2 = ptr_a;
24 cout << "copy ptr_a to ptr_a2 end" << endl;
25 ptr_a->show();
26 ptr_a.reset();
27 cout << "ptr_a reset" << endl;
28 ptr_a2->show();
29 }
30 cout << "end" << endl;
31 }
编译:
g++ -o auto_ptr auto_ptr.cpp
无error,无warning
用C++11编译
g++ -std=c++11 -o auto_ptr auto_ptr.cpp
warning 如下,不建议使用auto_ptr
auto_ptr.cpp: In function 'int main()':
auto_ptr.cpp:23:21: warning: 'auto_ptr' is deprecated (declared at /home/opt/gcc-4.8.2.bpkg-r4/gcc-4.8.2.bpkg-r4/include/c++/4.8.2/backward/auto_ptr.h:87) [-Wdeprecated-declarations]
auto_ptr<A> ptr_a2 = ptr_a;
ptr_a已经将所有权赋给ptr_a2,此时ptr_a无法继续使用
begin
copy ptr_a to ptr_a2 end
Segmentation fault (core dumped)
解决方法
为避免auto_ptr的使用导致代码运行异常,C++11引入unique_ptr, shared_ptr, weak_ptr,后续不再推荐使用auto_ptr
使用unique_ptr替换auto_ptr之后:
1 #include <iostream>
2 #include <memory>
3
4 using namespace std;
5
6 class A
7 {
8 public:
9 A(int num) { _num = num; }
10 ~A() { cout << "distroy A" << endl; }
11 int get() { return _num; }
12 void set(int num) { _num = num; }
13 void show() { cout << "num:" << _num << endl; }
14 private:
15 int _num;
16 };
17
18 int main()
19 {
20 cout << "begin" << endl;
21 {
22 unique_ptr<A> ptr_a(new A(10));
23 unique_ptr<A> ptr_a2 = ptr_a;
24 cout << "copy ptr_a to ptr_a2 end" << endl;
25 ptr_a->show();
26 ptr_a.reset();
27 cout << "ptr_a reset" << endl;
28 ptr_a2->show();
29 }
30 cout << "end" << endl;
31 }
编译:g++ -std=c++11 -o unique_ptr unique_ptr.cpp 报错
原因是unique_ptr删掉了拷贝构造函数,unique_ptr<A> ptr_a2 = ptr_a;无法编译通过,因此避免代码出现指针所有权转移之后的非法访问的问题
问题来了,如果确实需要多个指针引用同一个地址,如何操作?
shared_ptr就是解决多个指针引用统一地址的问题,shared_ptr可以通过拷贝构造函数产生多个指针,指向同一地址
通过全局的引用计数来对内存使用的指针计数,拷贝一次,引用计数+1,析构时,引用计数-1,当引用计数变成0时,才清理指针指向的内存空间
还是之前的代码
1 #include <iostream>
2 #include <memory>
3
4 using namespace std;
5
6 class A
7 {
8 public:
9 A(int num) { _num = num; }
10 ~A() { cout << "distroy A" << endl; }
11 int get() { return _num; }
12 void set(int num) { _num = num; }
13 void show() { cout << "num:" << _num << endl; }
14 private:
15 int _num;
16 };
17
18 int main()
19 {
20 cout << "begin" << endl;
21 {
22 shared_ptr<A> ptr_a(new A(10)); //引用计数+1
23 shared_ptr<A> ptr_a2 = ptr_a; //引用计数+1
24 cout << "copy ptr_a to ptr_a2 end" << endl;
25 ptr_a->show();
26 ptr_a.reset(); //引用计数-1
27 cout << "ptr_a reset" << endl;
28 ptr_a2->show();
29 }
30 //引用计数-1变为0,ptr_a2释放内存
31 cout << "end" << endl;
32 }
执行结果
begin
copy ptr_a to ptr_a2 end
num:10
ptr_a reset
num:10
distroy A
end
可以看到,在ptr_a拷贝到ptr_a2之后,ptr_a指向的地址依然有效,和ptr_a2共享同一块内存空间
在ptr_a销毁时,内存空间并没有销毁,因为此时ptr_a2还可能仍在使用,跳出29行时,引用计数归零,ptr_a2释放内存空间
有一种情况可能出现循环引用
如:
class A中的成员变量shared_ptr引用class B,class B中的成员变量shared_ptr引用class A
此时创建A和B的智能指针,引用计数都变成2,退出时,由于存在循环依赖,导致两个指针都无法释放,类似数据库中的死锁问题,不过数据库会进行死锁检测,自动回滚代价低的事务,从而解锁
智能指针是使用weak_ptr实现弱引用,避免相互依赖问题
weak_ptr在获取shared_ptr的指针时,引用计数并不加1,所以不会对原有的内存释放产生阻碍,但同时产生一个问题,如果内存已经释放,weak_ptr指向的内存已无法访问
所以weak_ptr在使用是,必须通过lock()方法,将返回值赋给shared_ptr之后再进行访问
代码示例:
1 #include <iostream>
2 #include <memory>
3
4 using namespace std;
5
6 class A
7 {
8 public:
9 A(int num) { _num = num; }
10 ~A() { cout << "distroy A" << endl; }
11 int get() { return _num; }
12 void set(int num) { _num = num; }
13 void show() { cout << "num:" << _num << endl; }
14 private:
15 int _num;
16 };
17
18 int main()
19 {
20 cout << "begin" << endl;
21 {
22 shared_ptr<A> ptr_a(new A(10));
23 weak_ptr<A> ptr_a2 = ptr_a;
24 cout << "copy ptr_a to ptr_a2 end" << endl;
25 if (shared_ptr<A> ptr_tmp = ptr_a2.lock()) {
26 cout << "ptr_tmp show" << endl;
27 ptr_tmp->show();
28 } else {
29 cout << "ptr_tmp get none" << endl;
30 }
31 ptr_a->show();
32 ptr_a.reset();
33 cout << "ptr_a reset" << endl;
34 if (shared_ptr<A> ptr_tmp = ptr_a2.lock()) {
35 cout << "ptr_tmp show again" << endl;
36 ptr_tmp->show();
37 } else {
38 cout << "ptr_tmp get none after ptr_a reset" << endl;
39 }
40 }
41 cout << "end" << endl;
42 }
执行结果
begin
copy ptr_a to ptr_a2 end
ptr_tmp show
num:10
num:10
distroy A
ptr_a reset
ptr_tmp get none after ptr_a reset
end
思考
代码25行,在使用weak_ptr获取shared_ptr并赋值给ptr_tmp后,引用计数是否+1?如果此时ptr_a销毁,ptr_tmp是否还能继续使用?
继续修改代码:
1 #include <iostream>
2 #include <memory>
3
4 using namespace std;
5
6 class A
7 {
8 public:
9 A(int num) { _num = num; }
10 ~A() { cout << "distroy A" << endl; }
11 int get() { return _num; }
12 void set(int num) { _num = num; }
13 void show() { cout << "num:" << _num << endl; }
14 private:
15 int _num;
16 };
17
18 int main()
19 {
20 cout << "begin" << endl;
21 {
22 shared_ptr<A> ptr_a(new A(10));
23 weak_ptr<A> ptr_a2 = ptr_a;
24 cout << "copy ptr_a to ptr_a2 end" << endl;
25 if (shared_ptr<A> ptr_tmp = ptr_a2.lock()) {
26 cout << "ptr_a reset" << endl;
27 ptr_a.reset();
28 cout << "ptr_tmp show" << endl;
29 ptr_tmp->show();
30 } else {
31 cout << "ptr_tmp get none" << endl;
32 }
33 //ptr_a->show();
34 //ptr_a.reset();
35 cout << "ptr_a reset" << endl;
36 if (shared_ptr<A> ptr_tmp = ptr_a2.lock()) {
37 cout << "ptr_tmp show again" << endl;
38 ptr_tmp->show();
39 } else {
40 cout << "ptr_tmp get none after ptr_a reset" << endl;
41 }
42 }
43 cout << "end" << endl;
44 }
执行结果
begin
copy ptr_a to ptr_a2 end
ptr_a reset
ptr_tmp show
num:10
distroy A
ptr_a reset
ptr_tmp get none after ptr_a reset
end
所以,解除依赖关系后,在使用阶段编码不当也会导致循环依赖问题,使用时要注意