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
所以,解除依赖关系后,在使用阶段编码不当也会导致循环依赖问题,使用时要注意