这是视频的笔记,编译器版本是Visual Studio 2022提供的MSVC v143,C++版本是20,使用其他版本也是可以的,因为智能指针的源码并没有使用新的C++特性。
这个视频讲的是unique_ptr。在看源码之前,建议大家先看一遍C++ reference关于unique_ptr的文档,了解一下它的接口。
unique_ptr的原理很简单,就是RAII,通过智能指针的构造和析构来管理资源。在构造的时候分配和创建被管理对象,在析构的时候销毁和释放被管理对象。源码中使用了大量的元编程,阅读起来会有一些难度。
VS有书签,大家可以把关键的代码标记一下,方便查找,因为unique_ptr的源码是分散在很多地方的。VS的一些快捷键也非常方便,比如:转到定义,查找所有引用,向前导航,向后导航。我把这些快捷键都绑定在了鼠标上,因为这个鼠标上的按键挺多的。
阅读源码会有些恼人,建议大家先把unique_ptr的源码精度一遍,这样后面阅读更复杂的shared_ptr和weak_ptr就会轻松很多。
声明
首先来看unique_ptr的声明:
template <class _Ty, class _Dx = default_delete<_Ty>>
class unique_ptr;
这是一个模板类,模板有两个参数:
- _Ty:被管理对象的类型。
- _Dx:删除器的类型。
删除器是用来销毁和释放被管理对象的,可以是以下这些类型:
- 函数对象。
- 函数对象的左值引用。
- 函数的左值引用。
函数对象是可以像函数一样使用函数调用运算符(提供了operator()函数)被调用的对象。函数的指针,仿函数,闭包,std::function都是函数对象。注意,函数和函数的引用不是函数对象,但是可以在需要函数对象的地方隐式转换成函数的指针(退化)。这些概念会在后面用到。
注意,函数不能作为模板参数,因为函数是保存在进程地址空间的代码区中的,没有办法直接保存在类或者对象中。但是函数的引用可以作为模板参数,因为对象可以使用成员变量来保存引用。
default_delete
unique_ptr提供了默认的删除器:
template <class _Ty>
struct default_delete;
template <class _Ty>
struct default_delete { // default deleter for unique_ptr
constexpr default_delete() noexcept = default;
template <class _Ty2, enable_if_t<is_convertible_v<_Ty2*, _Ty*>, int> = 0>
default_delete(const default_delete<_Ty2>&) noexcept {}
void operator()(_Ty* _Ptr) const noexcept /* strengthened */ { // delete a pointer
static_assert(0 < sizeof(_Ty), "can't delete an incomplete type");
delete _Ptr;
}
};
default_delete也是模板类,模板参数是被管理对象的类型。内部定义了3个成员函数:
- 默认构造函数。这里直接使用了default,还添加了两个说明符:constexpr和noexcept,这两个关键字在标准库中经常出现,因为标准库是非常注重性能的。后面还定义了其他的构造函数,所以编译器不会帮我们生成默认构造函数,那么这里就必须自己定义了(使用default)。
- 拷贝构造函数。这是模板函数,模板参数是另一个unique_ptr的被管理对象的类型。这个函数是为转移管理权提供服务的,将被管理对象的管理权从旧的unique_ptr转移到新的unique_ptr。这里使用SFINAE对管理权的转移做了限制,旧的被管理对象的指针必须能隐式转换成新的被管理对象的指针,这和普通指针的行为是一样的。通过其他类型的default_delete来拷贝构造、拷贝赋值、移动构造、移动赋值时,就会调用这个函数来检查(编译期)。使用SFINAE是为了在编辑代码时就给用户提示,而不是在编译时才提示。注意,C++20提供了更加好用的concept来代替SFINAE,后面的视频也会使用concept来仿写智能指针。
- 函数调用运算符。unique_ptr使用这个函数来销毁和释放被管理对象。因为是默认的删除器,这里就直接使用了delete。这里使用了const说明符,因为这个函数没有改变自己的成员变量(其实根本没有成员变量)。这里使用了noexcept说明符,注释中的
strengthened
说明C++标准并没有这么要求,而是MSVC的做法。调用这个函数的时机有3个:unique_ptr被销毁,管理权被转移(通过operator=),用户手动销毁被管理对象(通过reset()函数)。
这里提到了很多概念,不理解也没关系,后面还会反复提及。
定义
然后来看uniqur_ptr的定义:
template <class _Ty, class _Dx /* = default_delete<_Ty> */>
class unique_ptr { // non-copyable pointer to an object
};
template <class _Ty, class _Dx>
class unique_ptr<_Ty[], _Dx> { // non-copyable pointer to an array object
};
unique_ptr有两个版本:管理单个对象,管理数组。通过模板偏特化来区分。数组一般通过std::vector和std::string这样的容器来管理,所以这里只看管理单个对象的版本。
_Compressed_pair
unique_ptr只有一个成员:
private:
_Compressed_pair<_Dx, pointer> _Mypair;
};
unique_ptr需要在内部保存指针和删除器。为了性能,没有直接保存,而是通过_Compressed_pair
来保存:
template <class _Ty1, class _Ty2, bool = is_empty_v<_Ty1> && !is_final_v<_Ty1>>
class _Compressed_pair final : private _Ty1 { // store a pair of values, deriving from empty first
public:
_Ty2 _Myval2;
using _Mybase = _Ty1; // for visualization
template <class... _Other2>
constexpr explicit _Compressed_pair(_Zero_then_variadic_args_t, _Other2&&... _Val2) noexcept(
conjunction_v<is_nothrow_default_constructible<_Ty1>, is_nothrow_constructible<_Ty2, _Other2...>>)
: _Ty1(), _Myval2(_STD forward<_Other2>(_Val2)...) {}
template <class _Other1, class... _Other2>
constexpr _Compressed_pair(_One_then_variadic_args_t, _Other1&& _Val1, _Other2&&... _Val2) noexcept(
conjunction_v<is_nothrow_constructible<_Ty1, _Other1>, is_nothrow_constructible<_Ty2, _Other2...>>)
: _Ty1(_STD forward<_Other1>(_Val1)), _Myval2(_STD forward<_Other2>(_Val2)...) {}
constexpr _Ty1& _Get_first() noexcept {
return *this;
}
constexpr const _Ty1& _Get_first() const noexcept {
return *this;
}
};
template <class _Ty1, class _Ty2>
class _Compressed_pair<_Ty1, _Ty2, false> final { // store a pair of values, not deriving from first
public:
_Ty1 _Myval1;
_Ty2 _Myval2;
template <class... _Other2>
constexpr explicit _Compressed_pair(_Zero_then_variadic_args_t, _Other2&&... _Val2) noexcept(
conjunction_v<is_nothrow_default_constructible<_Ty1>, is_nothrow_constructible<_Ty2, _Other2...>>)
: _Myval1(), _Myval2(_STD forward<_Other2>(_Val2)...) {}
template <class _Other1, class... _Other2>
constexpr _Compressed_pair(_One_then_variadic_args_t, _Other1&& _Val1, _Other2&&... _Val2) noexcept(
conjunction_v<is_nothrow_constructible<_Ty1, _Other1>, is_nothrow_constructible<_Ty2, _Other2...>>)
: _Myval1(_STD forward<_Other1>(_Val1)), _Myval2(_STD forward<_Other2>(_Val2)...) {}
constexpr _Ty1& _Get_first() noexcept {
return _Myval1;
}
constexpr const _Ty1& _Get_first() const noexcept {
return _Myval1;
}
};
_Compressed_pair是一个pair,不仅可以保存两个对象,还有压缩的功能。模板参数有两个:删除器的类型,指针的类型。删除器可能是空类,比如默认的default_delete,这时删除器会占用一个字节的内存。如果_Compressed_pair直接将空类的删除器声明为自己的成员变量,那么就会占用4或8个字节的空间。_Compressed_pair通过让自己继承空类的删除器,来将多余的字节省去。这是通过EBO实现的。_Compressed_pair有两个版本:
- 主模板类:通过继承来保存空类的删除器,通过成员变量来保存指针。
- 偏特化类:通过成员变量来保存删除器和指针。
这两个版本的区别在于对删除器的操作不同,而对指针的操作是一样的。
_Compressed_pair有两个构造函数,都是模板函数,模板的参数有两个:_Other1,_Other2。_Other1是初始化删除器用的,_Other2是初始化指针用的。为什么有两个版本呢?首先,用户构造unique_ptr的时候可能不会传入自定义的删除器,这时unique_ptr会使用默认的删除器。那么unique_ptr的构造函数就有两种:没有传入删除器的版本,传入删除器的版本。_Compressed_pair的构造函数也就有两种:没有传入删除器的版本,传入删除器的版本。也就是说_Other1可能为空,也可能不为空。其次,用户构造unique_ptr的时候可能不会传入指针,这时unique_ptr会使用空指针。那么unique_ptr的构造函数就有两种:没有传入指针的版本,传入指针的版本。_Compressed_pair的构造函数也就有两种:没有传入指针的版本,传入指针的版本。也就是说_Other2可能为空,也可能不为空。综上所述,_Compressed_pair的构造有4种情况:
- _Other1空,_Other2空
- _Other1空,_Other2不空
- _Other1不空,_Other2空
- _Other1不空,_Other2不空
只使用一个构造函数能应对所有的情况吗?不能。因为使用一个构造函数是这样的:
public:
template <class... _Other1, class... _Other2>
_Compressed_pair(_Other1&&... _Val1, _Other2&&... _Val2);
所有的参数都会被_Other2捕获。那么就需要使用两个构造函数了,_Compressed_pair根据_Other1是否为空来区分两个构造函数,那么构造函数就会像这样:
public:
template <class... _Other2>
_Compressed_pair(_Other2&&... _Val2);
template <class _Other1, class... _Other2>
_Compressed_pair(_Other1&& _Val1, _Other2&&... _Val2);
情况1会选择版本1,其他情况会选择版本2。这是有问题的。既然编译器不能自动选择正确的版本,那么就需要户来手动选择了。_Compressed_pair通过标签分派来帮助编译器选择正确的版本:
struct _Zero_then_variadic_args_t {
explicit _Zero_then_variadic_args_t() = default;
}; // tag type for value-initializing first, constructing second from remaining args
struct _One_then_variadic_args_t {
explicit _One_then_variadic_args_t() = default;
}; // tag type for constructing first from one arg, constructing second from remaining args
这样就形成了源码中的构造函数。其实只使用一个标签就够了,用户不使用版本1就表明希望使用版本2。版本2也没必要使用可变参,因为删除器在unique_ptr中是作为第二个参数传入的,第一个参数是指针,用户肯定在传入删除器的同时也传入了指针,那么_Other2就不可能为空。
_Compressed_pair的构造函数有noexcept声明, 只有当删除器和指针的构造都不抛出异常时,_Compressed_pair的构造才能保证不会抛出异常。
下面来看怎么访问_Compressed_pair保存的删除器和指针。指针被声明成public成员,可以直接访问。删除器有两种情况。版本2的删除器被声明成public成员,可以直接访问。版本1的删除器是_Compressed_pair的空基类子对象,需要对_Compressed_pair进行类型转换才能访问。为了使用方便,_Compressed_pair为两种情况提供了统一的接口。注意,_Compressed_pair提供了非const和const版本的接口,而unique_ptr并不会用到const版本的_Compressed_pair。const版本返回的是const删除器,因为const _Compressed_pair不能修改自己的成员。
成员类型
unique_ptr定义了3个类型:
public:
using pointer = get_deleter_pointer_type<T, std::remove_reference_t<D>>::type;
using element_type = T;
using deleter_type = D;
分别是:指针的类型,被管理对象的类型,删除器的类型。get_deleter_pointer_type用来获取指针的类型,有两个版本,通过SFINAE来选择,如果删除器定义了指针的类型,则使用该类型,否则使用普通的指针:
template <class T, class D, class = void>
struct get_deleter_pointer_type {
using type = T*;
};
template <class T, class D>
struct get_deleter_pointer_type<T, D, std::void_t<typename D::pointer>> {
using type = D::pointer;
};
成员函数(拷贝)
unique_ptr只能移动,不能拷贝:
public:
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
成员函数(修改)
release:释放管理权。将unique_ptr保存的指针和空指针交换,然后返回之前保存的指针。
public:
pointer release() noexcept {
return _STD exchange(_Mypair._Myval2, nullptr);
}
reset:重置被管理对象。将unique_ptr保存的指针和用户传入的指针(默认为空)交换,然后调用保存的删除器来删除之前的被管理对象。注意,reset没有自检,执行reset(get())
是危险的,而执行reset(release())
是没有危险的。reset(get())
和reset()
一样都会删除被管理对象,但不会将指针置空。reset(release())
将管理权从unique_ptr转移到用户,再从用户转移回unique_ptr。注意,reset只是重置被管理对象,如果希望将删除器也一并重置,应该使用move。
public:
void reset(pointer _Ptr = nullptr) noexcept {
pointer _Old = _STD exchange(_Mypair._Myval2, _Ptr);
if (_Old) {
_Mypair._Get_first()(_Old);
}
}
swap:交换被管理对象和删除器。和另一个unique_ptr交换指针和删除器。注意,如果删除器的类型不是引用,则交换的是unique_ptr保存的删除器,如果删除器的类型是引用,则交换的是被引用的删除器。因为引用在初始化后就不能更改绑定的对象了,对引用的更改都是对原始对象的更改。
public:
void swap(unique_ptr& _Right) noexcept {
_Swap_adl(_Mypair._Myval2, _Right._Mypair._Myval2);
_Swap_adl(_Mypair._Get_first(), _Right._Mypair._Get_first());
}
成员函数(观察)
get:获取指针。
public:
_NODISCARD pointer get() const noexcept {
return _Mypair._Myval2;
}
get_deleter:获取删除器。const版本返回的是const删除器,因为const unique_ptr不能修改自己的成员变量。
public:
_NODISCARD _Dx& get_deleter() noexcept {
return _Mypair._Get_first();
}
_NODISCARD const _Dx& get_deleter() const noexcept {
return _Mypair._Get_first();
}
bool:检查是否管理了对象。
public:
explicit operator bool() const noexcept {
return static_cast<bool>(_Mypair._Myval2);
}
operator*:解引用指针。在C++标准中,如果指针为空,解引用会抛出异常,但是MSVC在这里添加了noexcept说明符,而C++ reference中是noexcept(noexcept(*std::declval<pointer>()))
。返回的是引用,但是没有添加const说明符,这和普通指针的行为是一样的。
operator->:获取指针。和普通指针的行为一样。
public:
_NODISCARD add_lvalue_reference_t<_Ty> operator*() const noexcept /* strengthened */ {
return *_Mypair._Myval2;
}
_NODISCARD pointer operator->() const noexcept {
return _Mypair._Myval2;
}
析构函数
析构函数:删除被管理对象。
public:
~unique_ptr() noexcept {
if (_Mypair._Myval2) {
_Mypair._Get_first()(_Mypair._Myval2);
}
}
构造函数(默认)
unique_ptr():默认构造函数。用户没有传入指针和删除器,所以使用空指针和默认删除器,调用_Compressed_pair的版本1构造函数来初始化_Mypair。
unique_ptr(nullptr_t):参数为空指针,行为和默认构造函数一样。unique_ptr(pointer _Ptr)
可以实现这个函数的功能,但是这里可以添加constexpr说明符,提高性能。
public:
template <class _Dx2 = _Dx, _Unique_ptr_enable_default_t<_Dx2> = 0>
constexpr unique_ptr() noexcept : _Mypair(_Zero_then_variadic_args_t{}) {}
template <class _Dx2 = _Dx, _Unique_ptr_enable_default_t<_Dx2> = 0>
constexpr unique_ptr(nullptr_t) noexcept : _Mypair(_Zero_then_variadic_args_t{}) {}
_Unique_ptr_enable_default_t用来约束删除器的类型,删除器需要满足两个条件:不是指针,可以默认构造。如果删除器的类型为指针,而指针会默认初始化为空,那么unique_ptr就不能正确地删除被管理对象了。删除器需要能够默认构造,所以它的类型不能是引用。这里使用conjunction_v而不是&&,是因为conjunction_v可以短路求值。
template <class _Dx2>
using _Unique_ptr_enable_default_t =
enable_if_t<conjunction_v<negation<is_pointer<_Dx2>>, is_default_constructible<_Dx2>>, int>;
在C++20中,可以使用concept来代替SFINAE:
template <class _Dx2>
concept _Unique_ptr_enable_default_t =
!is_pointer_v<_Dx2> && is_default_constructible_v<_Dx2>;
public:
template <_Unique_ptr_enable_default_t _Dx2 = _Dx>
constexpr unique_ptr() noexcept : _Mypair(_Zero_then_variadic_args_t{}) {}
template <_Unique_ptr_enable_default_t _Dx2 = _Dx>
constexpr unique_ptr(nullptr_t) noexcept : _Mypair(_Zero_then_variadic_args_t{}) {}
构造函数(传入指针)
unique_ptr(pointer _Ptr):参数为指针,使用默认删除器。
public:
template <class _Dx2 = _Dx, _Unique_ptr_enable_default_t<_Dx2> = 0>
explicit unique_ptr(pointer _Ptr) noexcept : _Mypair(_Zero_then_variadic_args_t{}, _Ptr) {}
构造函数(传入指针和删除器)
指针使用值传递,删除器使用引用传递。这是因为指针占用空间很小,而删除器占用空间可能很大。删除器有拷贝构造和移动构造两种初始化方式:
public:
unique_ptr(pointer p, const D& d);
unique_ptr(pointer p, D&& d);
根据删除器的类型D
的不同,这两个构造函数会被实例化成不同的形式:
- 非引用
A
,这时构造函数为:
public:
unique_ptr(pointer p, const A& d); // const D& -> const A&
unique_ptr(pointer p, A&& d); // D&& -> A&&
- 左值引用
A&
,这时构造函数为:
public:
unique_ptr(pointer p, A& d); // const D& -> const A& & -> A&
unique_ptr(pointer p, A& d); // D&& -> A& && -> A&
- 常量左值引用
const A&
,这时构造函数为:
public:
unique_ptr(pointer p, const A& d); // const D& -> const const A& & -> const A&
unique_ptr(pointer p, const A& d); // D&& -> const A& && -> const A&
当删除器为引用时,根据引用折叠,两个构造函数都会实例化成拷贝初始化的形式。为了使移动初始化的构造函数能够正确地实例化,应该避免引用折叠,方法是将模板参数的引用去除:
public:
unique_ptr(pointer p, const D& d);
unique_ptr(pointer p, remove_reference_t<D>&& d);
当删除器为引用时,不应该使用移动构造对其初始化,方法是将其删除:
public:
unique_ptr(pointer p, const D& d);
template <class D2 = D> requires (!is_reference_v<D2>)
unique_ptr(pointer p, D&& d);
template <class D2 = D> requires is_reference_v<D2>
unique_ptr(pointer, remove_reference_t<D>&&) = delete;
这样就形成了unique_ptr的构造函数。unique_ptr还使用is_constructible_v
对删除器的类型做了检查,使用is_constructible_v
而不是is_move_constructible_v
,是因为is_move_constructible_v
没有处理引用折叠。
public:
template <class _Dx2 = _Dx, enable_if_t<is_constructible_v<_Dx2, const _Dx2&>, int> = 0>
unique_ptr(pointer _Ptr, const _Dx& _Dt) noexcept : _Mypair(_One_then_variadic_args_t{}, _Dt, _Ptr) {}
template <class _Dx2 = _Dx,
enable_if_t<conjunction_v<negation<is_reference<_Dx2>>, is_constructible<_Dx2, _Dx2>>, int> = 0>
unique_ptr(pointer _Ptr, _Dx&& _Dt) noexcept : _Mypair(_One_then_variadic_args_t{}, _STD move(_Dt), _Ptr) {}
template <class _Dx2 = _Dx,
enable_if_t<conjunction_v<is_reference<_Dx2>, is_constructible<_Dx2, remove_reference_t<_Dx2>>>, int> = 0>
unique_ptr(pointer, remove_reference_t<_Dx>&&) = delete;
构造函数(移动)
构造函数(参数为同类型的unique_ptr):管理权从另一个unique_ptr转移到当前unique_ptr,所以这里使用release函数来处理指针。删除器有两种类型:
- 非引用:使用移动构造。另一个unique_ptr保存的删除器应该转移到当前unique_ptr。
- 引用:使用拷贝构造。另一个unique_ptr的删除器已经绑定到了删除器,当前unique_ptr的删除器应该也绑定到相同的删除器。
unique_ptr使用forward
来将删除器转换成合适的形式(右值、左值),由于get_deleter返回的是左值引用,这里会调用forward
的左值版本,返回值由删除器的类型决定:
- 非引用:返回右值引用。
- 引用:返回左值引用。
template <class _Ty>
_NODISCARD constexpr _Ty&& forward(
remove_reference_t<_Ty>& _Arg) noexcept { // forward an lvalue as either an lvalue or an rvalue
return static_cast<_Ty&&>(_Arg);
}
unique_ptr还使用is_move_constructible_v
对删除器的类型做了检查,如果删除器为非引用,会检查移动构造,如果删除器为引用,会检查拷贝构造:
public:
template <class _Dx2 = _Dx, enable_if_t<is_move_constructible_v<_Dx2>, int> = 0>
unique_ptr(unique_ptr&& _Right) noexcept
: _Mypair(_One_then_variadic_args_t{}, _STD forward<_Dx>(_Right.get_deleter()), _Right.release()) {}
构造函数(参数为其他类型的unique_ptr):管理权从另一个unique_ptr转移到当前unique_ptr。对指针和删除器的类型做了检查,需要满足条件:
- 另一个unique_ptr的被管理对象不是数组。
- 另一个unique_ptr的指针能隐式转换成当前unique_ptr的指针。
- 如果删除器是引用,则另一个unique_ptr的删除器和当前unique_ptr的删除器类型相同;如果删除器是非引用,则另一个unique_ptr的删除器能隐式转换成当前unique_ptr的删除器。
unique_ptr<Derived>
可以隐式转换成unique_ptr<Base>
。unique_ptr和_Compressed_pair没有对删除器是否可以构造进行检查。
public:
template <class _Ty2, class _Dx2,
enable_if_t<
conjunction_v<negation<is_array<_Ty2>>, is_convertible<typename unique_ptr<_Ty2, _Dx2>::pointer, pointer>,
conditional_t<is_reference_v<_Dx>, is_same<_Dx2, _Dx>, is_convertible<_Dx2, _Dx>>>,
int> = 0>
unique_ptr(unique_ptr<_Ty2, _Dx2>&& _Right) noexcept
: _Mypair(_One_then_variadic_args_t{}, _STD forward<_Dx2>(_Right.get_deleter()), _Right.release()) {}
注意,访问另一个unique_ptr的私有成员,需要将其声明为自己的友元:
private:
template <class, class>
friend class unique_ptr;
赋值函数
赋值函数(参数为空指针):重置被管理对象。
public:
unique_ptr& operator=(nullptr_t) noexcept {
reset();
return *this;
}
赋值函数(参数为同类型的unique_ptr):管理权从另一个unique_ptr转移到当前unique_ptr。删除器有两种类型:
- 非引用:使用移动构造。另一个unique_ptr保存的删除器应该移动到当前unique_ptr。当删除器为非引用时,构造函数和赋值函数对删除器的操作是不同的,构造函数可以通过拷贝或者移动来初始化删除器,而赋值函数只能通过移动来对删除器赋值。
- 引用:使用拷贝构造。另一个unique_ptr的删除器绑定的删除器应该拷贝到当前unique_ptr的删除器绑定的删除器。注意,引用在初始化后就不能更改了,之后对它的更改都是对原始对象的更改。当删除器为引用时,构造函数和赋值函数对删除器的操作是不同的,构造函数需要将引用绑定到删除器,而赋值函数需要对绑定的删除器赋值,因为引用只能绑定一次。
public:
template <class _Dx2 = _Dx, enable_if_t<is_move_assignable_v<_Dx2>, int> = 0>
unique_ptr& operator=(unique_ptr&& _Right) noexcept {
if (this != _STD addressof(_Right)) {
reset(_Right.release());
_Mypair._Get_first() = _STD forward<_Dx>(_Right._Mypair._Get_first());
}
return *this;
}
unique_ptr使用is_move_assignable_v
对删除器的类型做了限制,如果删除器为非引用,会检查移动赋值,如果删除器为引用,会检查拷贝赋值。注意,删除器为常量引用时,检查会失败。
赋值函数(参数为其他类型的unique_ptr):管理权从另一个unique_ptr转移到当前unique_ptr。对指针和删除器的类型做了检查,需要满足条件:
- 另一个unique_ptr的被管理对象不是数组。
- 另一个unique_ptr的指针能隐式转换成当前unique_ptr的指针。
- 如果删除器是非引用,可以移动赋值;如果删除器是引用,可以拷贝赋值。
public:
template <class _Ty2, class _Dx2,
enable_if_t<conjunction_v<negation<is_array<_Ty2>>, is_assignable<_Dx&, _Dx2>,
is_convertible<typename unique_ptr<_Ty2, _Dx2>::pointer, pointer>>,
int> = 0>
unique_ptr& operator=(unique_ptr<_Ty2, _Dx2>&& _Right) noexcept {
reset(_Right.release());
_Mypair._Get_first() = _STD forward<_Dx2>(_Right._Mypair._Get_first());
return *this;
}
非成员函数
make_unique
make_unique是模板函数,模板参数包括被管理对象的类型,被管理对象有3种:
- 非数组:参数列表用来构造被管理对象。
- 无界数组:参数为数组的大小。
- 有界数组:禁止。
template <class _Ty, class... _Types, enable_if_t<!is_array_v<_Ty>, int> = 0>
_NODISCARD unique_ptr<_Ty> make_unique(_Types&&... _Args) { // make a unique_ptr
return unique_ptr<_Ty>(new _Ty(_STD forward<_Types>(_Args)...));
}
template <class _Ty, enable_if_t<is_array_v<_Ty> && extent_v<_Ty> == 0, int> = 0>
_NODISCARD unique_ptr<_Ty> make_unique(const size_t _Size) { // make a unique_ptr
using _Elem = remove_extent_t<_Ty>;
return unique_ptr<_Ty>(new _Elem[_Size]());
}
template <class _Ty, class... _Types, enable_if_t<extent_v<_Ty> != 0, int> = 0>
void make_unique(_Types&&...) = delete;
C++20提供了is_unbounded_array_v
和is_bounded_array_v
来检查无界和有界数组:
template <class _Ty, class... _Types>
_NODISCARD unique_ptr<_Ty> make_unique(_Types&&... _Args) { // make a unique_ptr
return unique_ptr<_Ty>(new _Ty(_STD forward<_Types>(_Args)...));
}
template <class _Ty> requires is_unbounded_array_v<_Ty>
_NODISCARD unique_ptr<_Ty> make_unique(const size_t _Size) { // make a unique_ptr
using _Elem = remove_extent_t<_Ty>;
return unique_ptr<_Ty>(new _Elem[_Size]());
}
template <class _Ty, class... _Types> requires is_bounded_array_v<_Ty>
void make_unique(_Types&&...) = delete;
make_unique_for_overwrite
make_unique_for_overwrite使用默认初始化。
template <class _Ty, enable_if_t<!is_array_v<_Ty>, int> = 0>
_NODISCARD unique_ptr<_Ty> make_unique_for_overwrite() { // make a unique_ptr with default initialization
return unique_ptr<_Ty>(new _Ty);
}
template <class _Ty, enable_if_t<is_unbounded_array_v<_Ty>, int> = 0>
_NODISCARD unique_ptr<_Ty> make_unique_for_overwrite(
const size_t _Size) { // make a unique_ptr with default initialization
using _Elem = remove_extent_t<_Ty>;
return unique_ptr<_Ty>(new _Elem[_Size]);
}
template <class _Ty, class... _Types, enable_if_t<is_bounded_array_v<_Ty>, int> = 0>
void make_unique_for_overwrite(_Types&&...) = delete;
比较运算符
比较两个unique_ptr的指针,重点看==、<、<=>:
template <class _Ty1, class _Dx1, class _Ty2, class _Dx2>
_NODISCARD bool operator==(const unique_ptr<_Ty1, _Dx1>& _Left, const unique_ptr<_Ty2, _Dx2>& _Right) {
return _Left.get() == _Right.get();
}
template <class _Ty1, class _Dx1, class _Ty2, class _Dx2>
_NODISCARD bool operator<(const unique_ptr<_Ty1, _Dx1>& _Left, const unique_ptr<_Ty2, _Dx2>& _Right) {
using _Ptr1 = typename unique_ptr<_Ty1, _Dx1>::pointer;
using _Ptr2 = typename unique_ptr<_Ty2, _Dx2>::pointer;
using _Common = common_type_t<_Ptr1, _Ptr2>;
return less<_Common>{}(_Left.get(), _Right.get());
}
template <class _Ty1, class _Dx1, class _Ty2, class _Dx2>
_NODISCARD bool operator>=(const unique_ptr<_Ty1, _Dx1>& _Left, const unique_ptr<_Ty2, _Dx2>& _Right) {
return !(_Left < _Right);
}
template <class _Ty1, class _Dx1, class _Ty2, class _Dx2>
_NODISCARD bool operator>(const unique_ptr<_Ty1, _Dx1>& _Left, const unique_ptr<_Ty2, _Dx2>& _Right) {
return _Right < _Left;
}
template <class _Ty1, class _Dx1, class _Ty2, class _Dx2>
_NODISCARD bool operator<=(const unique_ptr<_Ty1, _Dx1>& _Left, const unique_ptr<_Ty2, _Dx2>& _Right) {
return !(_Right < _Left);
}
template <class _Ty1, class _Dx1, class _Ty2, class _Dx2>
requires three_way_comparable_with<typename unique_ptr<_Ty1, _Dx1>::pointer,
typename unique_ptr<_Ty2, _Dx2>::pointer>
_NODISCARD compare_three_way_result_t<typename unique_ptr<_Ty1, _Dx1>::pointer,
typename unique_ptr<_Ty2, _Dx2>::pointer>
operator<=>(const unique_ptr<_Ty1, _Dx1>& _Left, const unique_ptr<_Ty2, _Dx2>& _Right) {
// clang-format on
return _Left.get() <=> _Right.get();
}
比较unique_ptr的指针和nullptr,重点看==、<、<=>:
template <class _Ty, class _Dx>
_NODISCARD bool operator==(const unique_ptr<_Ty, _Dx>& _Left, nullptr_t) noexcept {
return !_Left;
}
template <class _Ty, class _Dx>
_NODISCARD bool operator<(const unique_ptr<_Ty, _Dx>& _Left, nullptr_t _Right) {
using _Ptr = typename unique_ptr<_Ty, _Dx>::pointer;
return less<_Ptr>{}(_Left.get(), _Right);
}
template <class _Ty, class _Dx>
_NODISCARD bool operator<(nullptr_t _Left, const unique_ptr<_Ty, _Dx>& _Right) {
using _Ptr = typename unique_ptr<_Ty, _Dx>::pointer;
return less<_Ptr>{}(_Left, _Right.get());
}
template <class _Ty, class _Dx>
_NODISCARD bool operator>=(const unique_ptr<_Ty, _Dx>& _Left, nullptr_t _Right) {
return !(_Left < _Right);
}
template <class _Ty, class _Dx>
_NODISCARD bool operator>=(nullptr_t _Left, const unique_ptr<_Ty, _Dx>& _Right) {
return !(_Left < _Right);
}
template <class _Ty, class _Dx>
_NODISCARD bool operator>(const unique_ptr<_Ty, _Dx>& _Left, nullptr_t _Right) {
return _Right < _Left;
}
template <class _Ty, class _Dx>
_NODISCARD bool operator>(nullptr_t _Left, const unique_ptr<_Ty, _Dx>& _Right) {
return _Right < _Left;
}
template <class _Ty, class _Dx>
_NODISCARD bool operator<=(const unique_ptr<_Ty, _Dx>& _Left, nullptr_t _Right) {
return !(_Right < _Left);
}
template <class _Ty, class _Dx>
_NODISCARD bool operator<=(nullptr_t _Left, const unique_ptr<_Ty, _Dx>& _Right) {
return !(_Right < _Left);
}
template <class _Ty, class _Dx>
requires three_way_comparable<typename unique_ptr<_Ty, _Dx>::pointer>
_NODISCARD compare_three_way_result_t<typename unique_ptr<_Ty, _Dx>::pointer> operator<=>(
const unique_ptr<_Ty, _Dx>& _Left, nullptr_t) {
// clang-format on
return _Left.get() <=> static_cast<typename unique_ptr<_Ty, _Dx>::pointer>(nullptr);
}
operator<<
将unique_ptr的指针插入到输出流,_Can_stream_unique_ptr
要求插入操作是有效的表达式。注意,如果指针的类型为char*
或char[]
,会打印字符串:
template <class _OutTy, class _PxTy, class = void>
struct _Can_stream_unique_ptr : false_type {};
template <class _OutTy, class _PxTy>
struct _Can_stream_unique_ptr<_OutTy, _PxTy, void_t<decltype(_STD declval<_OutTy>() << _STD declval<_PxTy>().get())>>
: true_type {};
template <class _Elem, class _Traits, class _Yty, class _Dx,
enable_if_t<_Can_stream_unique_ptr<basic_ostream<_Elem, _Traits>&, const unique_ptr<_Yty, _Dx>&>::value, int> = 0>
basic_ostream<_Elem, _Traits>& operator<<(basic_ostream<_Elem, _Traits>& _Out, const unique_ptr<_Yty, _Dx>& _Px) {
// write contained pointer to stream
_Out << _Px.get();
return _Out;
}
swap
swap:交换两个相同类型的unique_ptr。
template <class _Ty, class _Dx, enable_if_t<_Is_swappable<_Dx>::value, int> = 0>
void swap(unique_ptr<_Ty, _Dx>& _Left, unique_ptr<_Ty, _Dx>& _Right) noexcept {
_Left.swap(_Right);
}
hash
hash:对unique_ptr的指针hash。
template <class _Ty, class _Dx>
struct hash<unique_ptr<_Ty, _Dx>> : _Conditionally_enabled_hash<unique_ptr<_Ty, _Dx>,
is_default_constructible_v<hash<typename unique_ptr<_Ty, _Dx>::pointer>>> {
static size_t _Do_hash(const unique_ptr<_Ty, _Dx>& _Keyval) noexcept(
_Is_nothrow_hashable<typename unique_ptr<_Ty, _Dx>::pointer>::value) {
return hash<typename unique_ptr<_Ty, _Dx>::pointer>{}(_Keyval.get());
}
};