c++智能指针--shared_ptr
众所周知,c语言与c++需要自行管理动态的内存。许多代码写到最后,防止内存泄漏需要花费程序员大量的心力。在c++11标准中,提出了智能指针来帮助程序员管理动态内存。智能指针主要有三种,分别是shared_ptr
、unique_ptr
与weak_ptr
。本文主要讲的就是shared_ptr
使用中的问题。
shared_ptr
首先需要说明的是,shared_ptr
是一个模板类。为了让用户可以像使用内置指针一样使用它,这个类的设计者为它重载了解应用运算符*
、成员访问运算符->
、赋值运算符=
还有向bool类型的显式类型转换等。
我们都知道,在使用new
动态申请一块内存a后会返回一个指向这片内存的指针,对这个指针值进行拷贝赋值等操作,最后会出现很多个指针都指向a。但是通过指针释放a只能释放一次,如果释放了多次,就会出现错误。以前,大都是通过自己创建一种引用计数机制来管理这样的情况。
在c++11中,标准库提供了一种shared_ptr
智能指针类型。一个动态分配的对象可以在多个shared_ptr
之间共享,因此,shared_ptr
支持拷贝操作。
1
2shared_ptr<string> ptr1{ new string("hello") };
auto ptr2 = ptr1; //拷贝构造,ptr1与ptr2指向同一块动态内存(一个string对象)
原理分析
shared_ptr
内部包含两个指针,一个指向对象,另一个指向控制块(control
block),控制块中包含一个引用计数和其它一些数据。由于这个控制块需要在多个shared_ptr之间共享,所以它也是存在于heap
中的。shared_ptr
对象本身是线程安全的,也就是说shared_ptr
的引用计数增加和减少的操作都是原子的。
[sh2](/assets/img/shared_ptr/sh2.png)
构造选择
在初始化ptr
时一般有两种选择,分别是new
与make_shared
。
1
2shared_ptr<std::string> ptr{ new string("hello") };
auto ptr = std::make_shared<std::string>("hello");shared_ptr
时,需要执行两次new操作,一次在
heap 上为 string("hello") 分配内存,另一次在 heap
上为控制块分配内存。使用make_shared
来创建shared_ptr
会高效,因为make_shared
仅使用new操作一次,它的做法是在
heap 上分配一块连续的内存用来容纳 string("hello")
和控制块。同样,当shared_ptr
被析构时,也只需一次delete
操作。
销毁操作
一般情况下不需要考虑所指对象的销毁问题,只在指向数组时,与unique_ptr
不同,标准库并不提供shared_ptr<T[]>
,因此,使用shared_ptr
处理数组时需要显示指定删除行为,例如:
1
2
3
4
5
6shared_ptr<string> ptr1( new string[10],
[]( string *p ) {
delete[] p;
});
shared_ptr<string> ptr2( new string[10],
std::default_delete<string[]>() );
使用shared_ptr的坑--需要注意的问题1
用同一个内置指针初始化shared_ptr
的操作只能出现一次。
1
2
3int *p = new int{10};
shared_ptr<int> ptr1{ p };
shared_ptr<int> ptr2{ p }; // ERRORshared_ptr
时候就会分配一个控制块,这时存在两个控制块,也就是说存在两个引用计数。这显然是错误的,因为当这两个shared_ptr
被销毁时,对象将会被delete两次。
使用shared_ptr的坑--需要注意的问题2
在对象之间出现循环引用时,会使得共享指针引用计数不会降到0,也就不能销毁。循环引用示意图如下:
[am](/assets/img/shared_ptr/am.png)
1 |
|
编译运行结果为: 1
2
3xhy@ubuntu:~/cpp_learn/share_ptr$ ./test
one father's son:2
one son's father:2
参考资料
- [1] C++ Primer(第5版)
- [2] http://senlinzhan.github.io/2015/04/24/%E6%B7%B1%E5%85%A5shared-ptr/