勿在浮沙筑高台
本文的目标是掌握清楚 智能指针 shared_ptr 、unique_ptr 和weak_ptr 的用法, 涉及到的概念和理念。
概念
RAII(Resource Acquisition Is Initialization)
RAII 含义即是资源分配即初始化。
讲的是这样一个理念,将资源的管理放在一个类中,利用类的生命周期构造函数和析构函数来进行资源的管理和释放。用局部对象来表示资源。
这是c++ 编程中的最重要编程技法之一。
智能指针便是利用 RAII 的技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现却像一个指针
C++11 中提供了三种智能指针
分别是 shared_ptr , unique_ptr 和 weak_ptr 。
- shared_ptr 允许多个指针指向同一个对象,
- unique_ptr 则“独占”所指向的对象,
- weak_ptr 则是和share_ptr 相辅相成的伴随类
shared_ptr
share_ptr是一个类,它产生的是一个类对象,而不是一个原生的指针对象,但是为了减少类对象与针对对象使用的差异性,所以share_ptr类故意重载了两种常见的指针操作符: *和->。从而share_ptr与普通指针使用方式一样。简言之,就是share_ptr生成的一个包含类型指针容器对象,它封装了指针对象,对指针对象负全责,包括生成、释放等;
- shared_ptr多个智能指针可以指向相同对象;
- 能够自动释放所指向的对象
- 该对象和其相关资源会在“最后一个引用被销毁”时候释放。
- 对象创建完就应该直接交给智能指针管理;
- shared_ptr 采用引用计数器,多个shared_ptr种的 T*ptr 指向同一内存区域(同一对象),并共同维护同一个引用计数器。
- 初始化指针并将引用计数置为1;
- 当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数。
- 对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;这是因为左侧的指针指向了右侧指针所指向的对象,因此右指针所指向的对象的引用计数加1。调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。
- share_ptr完美支持标准容器,并且不需要担心资源泄漏。而标准容易在使用指针对象时需要特别的小心,对指针需要额外的管理 。
- 特别需要注意的是,share_ptr的转型函数不能使用c++常用的转型函数,即static_cast,dynamic_cast,const_cast,而要使用static_pointer_cast,dynamic_pointer_cast,const_pointer_cast。原因有两个:static_cast,dynamic_cast,const_cast的功能是转换成对应的模版类型,即static_cast<T*>其实是转换成类型为T的指针;前面说了share_ptr生成的一个包含类型指针容器对象,使用简单的c++转型函数是将share_ptr对象转型为模版指针对象,这完全违背了使用share_ptr的初衷(除非你确确实实有这种需要!),导致转型的模版指针对象不能采用share_ptr进行管理。因为上面的两个原因:share_ptr为了支持转型,所以提供了类似的转型函数即static_pointer_cast
,从而使转型后仍然为shared_pointer对象,仍然对指针进行管理;
Member functions
构造函数 不谈拷贝构造的话,shared_ptr的基本构造方式有4种:
- 无参数构造。
- 传入一个指针构造一个shared_ptr
- 传入一个指针和一个删除器构造一个shared_ptr。
- 传入一个指针、一个删除器以及一个allocator构造一个shared_ptr。
- 当然还有一些其他的,例如从auto_ptr从weak_ptr从null_ptr构造。
析构函数 用来释放智能指针,至于是否释放智能指针所管理的对象,取决于成员use_count的值;
- 如果use_count > 1,则引用计数器减一;
- 如果use_count=1,则引用计数器置零,资源释放。
- 如果use_count=0,则说明此智能指针本来就没有指向对象。
-
赋值操作后,赋值前原来的 _refCount要自减 , 计数器_refCount要做++ ;
-
交换,使两个智能指针分别指向对方的对象。
reset 复位操作,不再指向对象,原来所指向对象的_refCount要自减
get 获取对象的原始指针。
operator* 相当于*get()
operator-> 可以像原指针一样,指向原指针的成员。
use_count 返回当前所指对象的引用计数。
unique 检查Usecount() == 1
[operator bool](http://www.cplusplus.com/reference/memory/shared_ptr/operator bool/) 检查
get()!=NULL
-
Owner-based ordering (public member function template )
Non-member functions
swap 交换shared_ptr 所指对象
-
Relational operators ==, !=, <, <=, >, >= (function template )
[ostream operator<<](http://www.cplusplus.com/reference/memory/shared_ptr/operator<
跟原始指针的操作一样,流操作。
-
Make shared_ptr (function template )
-
Allocate shared_ptr (function template )
-
Static cast of shared_ptr (function template )
-
Dynamic cast of shared_ptr (function template )
-
Const cast of shared_ptr (function template )
-
Get deleter from shared_ptr (function template )
多线程问题
shared_ptr的线程安全的定义在boost的文档中有明确的说明:
一个shared_ptr对象可以被多个线程同时read
两个shared_ptr对象,指向同一个raw指针,两个线程分别write这两个shared_ptr对象,是安全的。包括析构。
多个线程如果要对同一个shared_ptr对象读写,是线程不安全的
也就是说,唯一需要注意的就是:多个线程中对同一个shared_ptr对象读写时需要加锁。但是即使是加锁也有技巧。比较好的方式是:
示例代码
1 |
|
std::weak_ptr
引入是为了解决 std::share_ptr 的链接环问题。c++
一般结合强智能指针使用,它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是强引用的 shared_ptr. weak_ptr只是提供了对管理对象的一个访问手段;weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 不会引起引用记数的增加或减少。
std::unique_ptr
unique_ptr 不共享它的指针。它无法复制到其他 unique_ptr,无法通过值传递到函数,也无法用于需要副本的任何标准模板库 (STL) 算法。只能移动
手册
后续
智能指针shared_ptr的实现
主要的问题
- shared_ptr 整体是如何实现的;
- shared_ptr 计数器的实现;
- shared_ptr 怎么实现-> 操作,访问所指向对象的成员就像访问本身的成员一样。