std::unique_ptr·
📌本文使用wolai制作,原文链接:std::unique_ptr
unique_ptr是c++11引入的智能指针之一,对所wrap的对象具有独一管理权。本文做详细分析。
分析环境: gcc 8.3.0
1. 类图·
classDiagram class Deleter{ } class __uniq_ptr_impl~_Tp,_Dp~{ // 内部定义了pointer类型 - _M_t: std::tuple< pointer, deleter > } class unique_ptr~_Tp, _Dp=default_delete<_Tp>~ { // 使用 __uniq_ptr_impl 的pointer类型,作为pointer类型 - _M_t: __uniq_ptr_impl< _Tp, _Dp > } Deleter <.. __uniq_ptr_impl~_Tp,_Dp~ __uniq_ptr_impl~_Tp,_Dp~ <.. unique_ptr~_Tp, _Dp=default_delete<_Tp>~
2. 基本声明·
1 | template <typename _Tp, typename _Dp = default_delete<_Tp>> |
unique_ptr
的 primary template具有两个模板参数,一个是要管理的类型_Tp
一个是删除器,默认参数为default_delete<_Tp>
. 还有一个偏特化,接受数组类型,此时的删除器需要user自定义类型。
3. deleter·
先看删除器的实现。 非数组类型的:
1 | /// Primary template of default_delete, used by unique_ptr |
本质上是一个functor,有一个默认构造函数和一个拷贝构造函数,但是拷贝构造函数是一个模板成员函数,enable的条件是,能从_Up*
类型转换为_Tp*
类型。这是什么意思?举个例子:
1 | std::unique_ptr<int> intPtr(new int(10)); |
functor的实现是直接delete __ptr
。
另一个是数组类型的deleter:
1 | template<typename _Tp> |
和非数组类型基本一样,只不过删除调用的是delete[]
4. __uniq_ptr_impl
·
本类是unique_ptr
实现的核心类,先分析:
4.1. 构造函数·
构造函数1:
1 | __uniq_ptr_impl(pointer __p) : _M_t() { _M_ptr() = __p; } |
本质上__uniq_ptr_impl
就是一个tuple,tuple 0 index是指针,1 index是_Dp
。
为什么不用pair? tuple有优化?
std::tuple采用了EBO技术, _Dp类型通常是个空struct,这样std::tuple就退化为一个指针,所以, std::unique_ptr + 默认deleter(或自定义了deleter,但是是个empty class) 它的内存占用开销和raw pointer是一样的!
基本构造函数中,初始化tuple,
并把index 0复制为__p
.
📌初始化tuple的时候,会调用deleter的默认构造函数
构造函数2:
1 | template<typename _Del> |
直接把deleter
传入并转发给_M_t
(tuple)构造。
4.2. pointer类型·
__uniq_ptr_Impl
有个相对复杂的类型,即tuple的pointer类型。
1 | template <typename _Tp, typename _Dp> |
这是一个class template. 且有一个偏特化的版本。
对于primary template来说,type 就是 _Up*
对于偏特化版本,如果传入的_Dp
在remote_reference
后持有pointer
类型,则使用自定义deleter的pointer类型作为整个__uniq_ptr_impl
pointer类型。
也就是说:
- 有自定义删除器且自定义删除器有**
pointer类型
:** 使用自定义删除器提供的pointer
类型作为指针类型。 - 否则 使用
_Up*
作为指针类型。
5. unique_ptr非数组类型实现·
先看成员变量和type:
1 | template <class _Up> |
内部持有了一个别名模板,引用自__uniq_ptr_impl
,看下实现:
1 | using _DeleterConstraint = enable_if< |
这句话的意思是,检测传入的deleter类型不能是一个指针,并且持有默认构造器。
5.1. 构造函数·
只看常用的构造函数:
1 | /** Takes ownership of a pointer. |
也是一个模板构造函数,SFINA机制保证,传入的_Dp
类型不能是指针,同时具有默认构造函数。
再看移动构造:
1 | /// Move constructor. |
把rhs只有的指针释放,同时把deleter转发。
5.2. 析构函数·
1 | /// Destructor, invokes the deleter if the stored pointer is not null. |
5.3. operator=(&&) move 持有权·
1 | // Assignment. |
通过reset,将pointer所有权从rhs转移到this指针。重新复制deleter。
📌注意:这个过程不是原子的。 如果在move unique_ptr过程中,出现并发,是可能有问题的。
5.4. 判空·
unique_ptr 重载了bool,所以可以直接拿unique_ptr
对象在if语句中判空:
1 | /// Return @c true if the stored pointer is not null. |
实际上就是看内部指针是否为空。
5.5. *
和→
·
1 | /// Dereference the stored pointer. |
6. unique_ptr 数组类型实现·
偏特化声明:
1 | template<typename _Tp, typename _Dp> |
这样接收的类型为数组类型时,会有限匹配此特化版本。如:
1 | std::unique_ptr<int[]> b(new int[10]); |
实际上匹配展开为:
1 | std::unique_ptr<int[], std::default_delete<int[]> > b = std::unique_ptr<int[], std::default_delete<int[]> >(new int[10]); |
为什么数组类型的
_Dp
没有默认值,也能自定匹配到default_delete[]
? 模板默认值只用在primary template中声明即可。因为primary template声明为:
1
2
3 template <typename _Tp, typename _Dp = default_delete<_Tp>>
class unique_ptr所以能自动匹配。
unique_ptr数组类型的实现和非数组类型基本完全一致。区别在于多提供一些数组访问符号。如
1 | /// Access an element of owned array. |
7. make_unique·
1 | /// std::make_unique for single objects |
make_unique 提供了single object和arrary类型的快速make函数。但是和调用构造函数+new object的方式没什么不同。(shared_ptr就不一样)。
8. 总结·
本文档深入分析了C++11标准库中的unique_ptr
智能指针,重点探讨了其内部实现、使用场景以及与之相关的概念,包括deleter
、__uniq_ptr_impl
核心类等。unique_ptr
是一种独占所有权的智能指针,它通过__uniq_ptr_impl
类来实现对资源的管理,确保每个被管理的对象都只有一个所有者。
unique_ptr
的基本声明包含一个模板参数_Tp
表示要管理的对象类型,以及可选的第二个模板参数_Dp
用于指定删除器类型,默认值为default_delete<_Tp>
。这种灵活性允许用户根据需要提供自定义的删除逻辑。
文章首先介绍了deleter
的概念,它是unique_ptr
中用于处理资源释放的部分。非数组类型的deleter
实现了一个简单的析构函数,而数组类型的deleter
则提供了对整个数组进行安全释放的方法。
__uniq_ptr_impl
是unique_ptr
实现的核心类,它内部封装了一个指向对象的指针和一个用于删除该对象的deleter
。通过_M_t
成员变量,该类实现了对指针和删除器的管理。此外,还讨论了__uniq_ptr_impl
的构造函数、pointer
类型和unique_ptr
的成员函数(包括赋值运算符、析构函数和各种访问操作符),以及如何使用make_unique
辅助函数创建新的unique_ptr
实例。
整体而言,文档全面地概述了unique_ptr
的设计思想、用法及其在现代C++开发中的重要作用,为读者提供了一种高效、安全地管理动态分配资源的方式。