C++的智能指针

上一篇笔记提到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;

 实际运行结果,运行到代码25行时出core,原因是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

 所以,解除依赖关系后,在使用阶段编码不当也会导致循环依赖问题,使用时要注意

 

posted @ 2018-08-07 14:46  Cony365  阅读(259)  评论(0编辑  收藏  举报