【智能指针1】unique_ptr源码剖析

这是视频的笔记,编译器版本是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,还添加了两个说明符:constexprnoexcept,这两个关键字在标准库中经常出现,因为标准库是非常注重性能的。后面还定义了其他的构造函数,所以编译器不会帮我们生成默认构造函数,那么这里就必须自己定义了(使用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_vis_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());
    }
};
  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值