C++智能指针的使用与实现

在C++史前时代只有一种智能指针std::auto_ptr<T>,它的作用方式类似一个lock_guard<T>,或者经过封装的RAII。但在使用中发现,依托于RAII是不够的,为了方便地实现更复杂逻辑下的资源管理,我们需要从资源的所有权上对智能指针进行更加细致的分类。在C++11之后,标准库引入了std::shared_ptr<T>std::unique_ptr<T>std::weak_ptr<T>来替换之前的std::auto_ptr<T>
截至目前为止,我基本没怎么用过智能指针,一方面之前做的项目都比较局限,使用RAII或者对象池会更方便,另一方面智能指针和对C风格的兼容性也不是很好,例如很多C风格的代码要求bit-wise而不是member-wise的操作,而智能指针并不是trivial的,而且具有传染性,所以往往适用不了。
【未完待续】

auto_ptr

shared_ptr

正确使用shared_ptr

构造函数、删除器与分配器

std::shared_ptr构造函数有12种之多,这里只列举几种重要的

1
2
3
4
5
6
7
8
9
// 构造一个空的智能指针
constexpr shared_ptr() noexcept;
constexpr shared_ptr( std::nullptr_t ) noexcept;
// 这是最常用的
template<class Y> explicit shared_ptr( Y* ptr );
// 这里在后面加了一个删除器的参数
template<class Y, class Delete > shared_ptr( Y* ptr, Deleter d );
// 这里又添加了一个分配器的函数
template<class Y, class Deleter, class Alloc> shared_ptr( Y* ptr, Deleter d, Alloc alloc );

删除器和分配器构成了智能指针的主要特性之一。我们知道智能指针的重要特点就是自动帮助我们管理资源,它们解决了何时销毁对象的难题WHEN,但同时也让我们可以自行定义如何创建和销毁对象的次要问题HOW。标准库为我们提供了两个标准的std::default_delete<T>的实现,其内容是非常的简单,即直接调用deletedelete [],在这里列出了后者的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template<class _Ty> struct default_delete<_Ty[]>
{
// 一个默认的构造函数`shared_from_this()`
// 一个默认的构造函数
constexpr default_delete() _NOEXCEPT = default;
// 一个默认的复制构造函数
template<class _Uty, class = typename enable_if<is_convertible<_Uty(*)[], _Ty(*)[]>::value, void>::type>
default_delete(const default_delete<_Uty[]>&) _NOEXCEPT {}

template<class _Uty, class = typename enable_if<is_convertible<_Uty(*)[], _Ty(*)[]>::value, void>::type>
void operator()(_Uty *_Ptr) const _NOEXCEPT
{
static_assert(0 < sizeof (_Uty), "can't delete an incomplete type");
delete[] _Ptr;
}
};

在这里需要注意,在C++17前后,std::shared_ptr创建数组的行为仍然是不同的。

  1. 在C++17前
    创建数组的时候需要手动指定删除器

    1
    std::shared_ptr<int> sp(new int[10], array_deleter<int>());
  2. 从C++17开始
    创建数组需要手动指定数组类型int[],而不再可以使用int了。

    1
    std::shared_ptr<int[]> sp(new int[10]);

循环引用与weak_ptr

正确使用shared_ptr与裸指针

我们知道智能指针是有传染性的,这意味着我们要避免同时使用raw pointer和智能指针,也要注意不能显式或者隐式地让多个智能指针同时管理同一个raw pointer。我们进一步地探讨这个问题,shared_ptr的主要创建方式有三种:

  1. make_shared函数
    这个函数是*Effective Modern C++*所推荐的示例,它会创建一个控制块和一个对象。根据cppreference的介绍,这个函数有5个重载

    1
    2
    3
    4
    5
    6
    7
    8
    9
    template<class T, class... Args> shared_ptr<T> make_shared( Args&&... args );
    // 从C++20开始,这里T是数组U[]
    template<class T> shared_ptr<T> make_shared(std::size_t N);
    // 从C++20开始,这里T是数组U[N]
    template<class T> shared_ptr<T> make_shared();
    // 从C++20开始,这里T是数组U[]
    template<class T> shared_ptr<T> make_shared(std::size_t N, const std::remove_extent_t<T>& u);
    // 从C++20开始,这里T是数组U[N]
    template<class T> shared_ptr<T> make_shared(const std::remove_extent_t<T>& u);

    我们注意一下这里的初始化是小括号初始化而不是C++11新规定的uniform初始化,即花括号初始化,例如下面的语句会创建10个20,如果我们想放两个元素10和20进去就要显式创建一个初始化列表

    1
    2
    3
    4
    5
    6
    7
    // 10个20
    auto upv = std::make_shared<std::vector<int>>(10, 20);
    // 10, 20
    // create std::initializer_list
    auto initList = { 10, 20 };
    // create std::vector using std::initializer_list ctor
    auto spv = std::make_shared<std::vector<int>>(initList);
  2. shared_ptr构造函数
    这种情况下我们将一个裸指针传给shared_ptr,这时就可能将裸指针泄露出去,从而导致可能的double free问题。因此在*Effective Modern C++*的条款19中强调best practice是我们写成将new语句写到参数列表里面

    1
    std::shared_ptr<Widget> spw1(new Widget);

    特别地,我们也可以从一个unique_ptr构造shard_ptr,这时候我们和上面的裸指针是类似的。

使用shared_from_this传出this

下面看一个典型的错误,我们试图在类T里面,传出shared_ptr指针。错误的原因是返回的shared_ptr采用独立的控制块,这导致this同时被两组share_ptr管理,会导致double free的问题。

1
2
3
4
5
class Widget {
std::shared_ptr<Widget> GetPtr() {
return shared_ptr<Widget>(this); // 错误
}
};

仔细一想,原因是这里面的this是一个raw pointer,那在内部直接定义一个std::shared_ptr<T*>(this)么,然后返回么?别的不论,这毫无疑问会导致循环引用。其实在对象内部传出this是非常常见的,例如bind系列的函数,会使用this作为一个context。为了能够正确使用this,就得继承一个std::enable_shared_from_this<T>,这样就可以使用shared_from_this()这个shared_ptr作为this的化身,如下所示。

1
2
3
4
5
struct Widget: public std::enable_shared_from_this<Widget>{
void process(){
processedWidgets.emplace_back(shared_from_this());
}
};

书中甚至对这种继承一个以自己为模板参数的父类的方法介绍了一种专门的称呼,叫The Curiously Recurring Template Pattern(CRTP)。
在使用shared_from_this()时我们需要注意以下问题:

  1. 二次析构
  2. 在构造函数中不能使用shared_from_this(),否则会抛出std::bad_weak_ptr

这个类的实现原理会在后面介绍。

使用make函数而不是使用智能指针的构造函数

这个来自于*Effective Modern C++*的条款21。原因之一是make函数是异常安全的,下面的代码可能导致内存泄露

1
processWidget(std::shared_ptr<Widget>(new Widget), computePriority()); // potential resource leak!

原因是什么呢?我们考虑这个调用的过程可以分为下面三个阶段:

  1. 创建new Widget
  2. 构造std::shared_ptr<Widget>
  3. 计算computePriority()

根据C++标准,这三个的求值顺序是UB的。我们考虑编译器产生1/3/2这样的执行顺序,并且此时computePriority()产生了异常,此时步骤1中new出来的对象就泄露了。
此外,对shared_ptr来说,使用make_shared函数还能提高效率。这是由于创建new Widget和控制块分配两次内存,而使用make_shared函数可以一次分配完。我们来看看标准库的实现,在这里我们看到只分配了一个_Ref_count_obj<_Ty>的对象,这个对象实际上继承了我们上面看到的_Ref_count_base的子类,它有一个typename aligned_union<1, _Ty>::type _Storage的字段管理了我们实际的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template<class _Ty, class... _Types> inline
shared_ptr<_Ty> make_shared(_Types&&... _Args)
{ // make a shared_ptr
_Ref_count_obj<_Ty> *_Rx =
new _Ref_count_obj<_Ty>(_STD forward<_Types>(_Args)...);

shared_ptr<_Ty> _Ret;
_Ret._Resetp0(_Rx->_Getptr(), _Rx);
return (_Ret);
}

// In _Ref_count_obj's definition
template<class... _Types> _Ref_count_obj(_Types&&... _Args) : _Ref_count_base()
{ // construct from argument list
::new ((void *)&_Storage) _Ty(_STD forward<_Types>(_Args)...);
}

此外从之前的讨论中我们看到make_shared杜绝了我们看到裸指针的一切可能性,因为它在函数内部创建了智能指针所指向类的实例,因此也更安全。

shared_ptr的结构与实现

我们以PJ Plauger的STL实现为例来查看这个智能指针的实现

基类_Ptr_base

std::shared_ptr继承了_Ptr_base,里面持有了两个指针,第一个就是实际的裸指针_Ptr,另一个是控制块指针_Rep。所有的控制块包括_Ref_count<T>_Ref_count_del<T>_Ref_count_del_alloc<T>_Ref_count_obj<T>_Ref_count_obj_alloc<T>,都继承自_Ref_count_base

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// shared_ptr的基类
template<class _Ty>
class _Ptr_base
{ // base class for shared_ptr and weak_ptr
/* ... */
private:
// 这是真实资源的指针
_Ty *_Ptr;
// 这是shared_ptr外挂式的控制块
_Ref_count_base *_Rep;
// 所有的实例化的_Ptr_base都互为友元
template<class _Ty0>
friend class _Ptr_base;
};
};

_Ref_count_base就是所有控制块对象的基类。它主要包含两个成员_Uses_Weaks,表示管理的shared_ptrweak_ptr的数量。注意在涉及引用计数的部分,都要是原子的,这里默认使用了Windows的互锁函数,详情可参见文章《并发编程重要概念及比较》

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class _Ref_count_base
{ // common code for reference counting
private:
// 删除裸指针
virtual void _Destroy() _NOEXCEPT = 0;
// 删除自己
virtual void _Delete_this() _NOEXCEPT = 0;

private:
_Atomic_counter_t _Uses;
_Atomic_counter_t _Weaks;

protected:
_Ref_count_base()
{ // construct
// 在构造时我们初始化引用计数都为**1**
_Init_atomic_counter(_Uses, 1);
_Init_atomic_counter(_Weaks, 1);
}
public:
// 虚析构函数
virtual ~_Ref_count_base() _NOEXCEPT
{ // ensure that derived classes can be destroyed properly
}

bool _Incref_nz()
{ // increment use count if not zero, return true if successful
...
};

// 这里直接进行强转,以便调用互锁函数
#define _MT_INCR(x) _InterlockedIncrement(reinterpret_cast<volatile long *>(&x))
#define _MT_DECR(x) _InterlockedDecrement(reinterpret_cast<volatile long *>(&x))

void _Incref()
{ // increment use count
_MT_INCR(_Uses);
}

void _Incwref()
{ // increment weak reference count
_MT_INCR(_Weaks);
}
...

如果强引用数为0,则销毁持有的对象,并自减弱引用数。
如果弱引用数为0,则销毁公共引用块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
void _Decref()
{ // decrement use count
if (_MT_DECR(_Uses) == 0)
{ // destroy managed resource, decrement weak reference count
_Destroy();
_Decwref();
}
}

void _Decwref()
{ // decrement weak reference count
if (_MT_DECR(_Weaks) == 0)
_Delete_this();
}
...

初始化过程的实现

进一步研究上面列出的构造函数中的实现,我们发现它们引用了下面三个函数之一,分别是适用于构造函数是否指定了Deleter和Allocator的情况。这里看到shared_ptr的某些构造函数是会抛出异常的,为了handle住异常,书中的best practice建议创建智能指针使用make_XXX而不是构造函数。

首先查看三个_Resetp函数,这些函数用来接管一个裸指针_Px。此时控制块肯定是不存在的,因此_Resetp需要创建一个全新的控制块,因此这些函数实际上对应通过裸指针创建shared_ptr的构造函数。

reset函数是shared_ptr中的一个重要的成员函数。它的作用是释放当前管理的对象,在调用后*this就不再有效,并且被释放管理对象的控制块的引用计数会减1。reset函数在释放之外,还可以同时接受一个新的指针作为参数,表示管理这个新的指针。我们稍后会看到一组_Reset函数,它们则处理较为复杂的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
private:
template<class _Ux>
void _Resetp(_Ux *_Px)
{ // release, take ownership of _Px
_TRY_BEGIN // allocate control block and reset
// 注意_Ref_count在创建时两个引用计数都为1了,因为它继承了_Ref_count_base,详见_Ref_count_base相关代码
_Resetp0(_Px, new _Ref_count<_Ux>(_Px));
_CATCH_ALL // allocation failed, delete resource
delete _Px;
_RERAISE;
_CATCH_END
}

template<class _Ux, class _Dx>
void _Resetp(_Ux *_Px, _Dx _Dt)
{ // release, take ownership of _Px, deleter _Dt
_TRY_BEGIN // allocate control block and reset
_Resetp0(_Px, new _Ref_count_del<_Ux, _Dx>(_Px, _Dt));
_CATCH_ALL // allocation failed, delete resource
_Dt(_Px);
_RERAISE;
_CATCH_END
}

template<class _Ux, class _Dx, class _Alloc>
void _Resetp(_Ux *_Px, _Dx _Dt, _Alloc _Ax)
{ // release, take ownership of _Px, deleter _Dt, allocator _Ax
typedef _Ref_count_del_alloc<_Ux, _Dx, _Alloc> _Refd;
typedef _Wrap_alloc<_Alloc> _Alref0;
typename _Alref0::template rebind<_Refd>::other _Alref(_Ax);

_TRY_BEGIN // allocate control block and reset
_Refd *_Pref = _Alref.allocate(1);
_Alref.construct(_Pref, _Px, _Dt, _Ax);
_Resetp0(_Px, _Pref);
_CATCH_ALL // allocation failed, delete resource
_Dt(_Px);
_RERAISE;
_CATCH_END
}
...

_Resetp0

_Resetp0是所有_Resetp的终点,包含了两个调用,我们将对此进行探讨

1
2
3
4
5
6
7
public:
template<class _Ux>
void _Resetp0(_Ux *_Px, _Ref_count_base *_Rx)
{ // release resource and take ownership of _Px
this->_Reset0(_Px, _Rx);
_Enable_shared(_Px, _Rx);
}
  1. this->_Reset0
    _Reset0基类_Ptr_base中有定义,并且派生类std::shared_ptr也没有进行覆盖,它的功能是切换智能指针管理另一个资源。可以看到,如果此时智能指针已经绑定了控制块,那么就调用_Decref自减一次。代码可查看上面_Ptr_base的实现。因为稍后智能指针即将管理新的_Other_rep控制块和_Other_ptr对象指针了。容易看到,在被_Resetp0调用时_Rep是空指针,所以直接赋值。

    1
    2
    3
    4
    5
    6
    7
    8
    void _Reset0(_Ty *_Other_ptr, _Ref_count_base *_Other_rep)
    { // release resource and take new resource
    // 这里的_Rep是_Ptr_base持有的_Ref_count_base *
    if (_Rep != 0)
    _Rep->_Decref();
    _Rep = _Other_rep;
    _Ptr = _Other_ptr;
    }

    既然如此,为什么我们不增加下_Other_rep的调用数目呢?其实是会增加的,只是不在_Other_rep之中。首先根据上面的讨论,当_Other_rep是新被创建的对象时,它的两个引用计数就默认被设为0了。其次,当_Other_rep是由其它智能指针创建的,也就是说我们此时将智能指针是从另一个智能指针创建的时,会调用之前提到的_Reset函数,而这个函数在自增对方的控制块_Other_rep后才会调用_Reset0

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    template<class _Ty2, class = typename enable_if<is_convertible<_Ty2 *, _Ty *>::value, void>::type>
    shared_ptr(const shared_ptr<_Ty2>& _Other) _NOEXCEPT
    { // construct shared_ptr object that owns same resource as _Other
    this->_Reset(_Other);
    }

    template<class _Ty2>
    void _Reset(const _Ptr_base<_Ty2>& _Other)
    { // release resource and take ownership of _Other._Ptr
    _Reset(_Other._Ptr, _Other._Rep);
    }

    void _Reset(_Ty *_Other_ptr, _Ref_count_base *_Other_rep)
    { // release resource and take _Other_ptr through _Other_rep
    if (_Other_rep)
    _Other_rep->_Incref();
    _Reset0(_Other_ptr, _Other_rep);
    }
  2. _Enable_shared
    这里的_Enable_shared用来处理继承了enable_shared_from_this<T>的情况,在下面的讨论中详细了解有关这个函数和enable_shared_from_this的实现。

enable_shared_from_this

上文讨论了当需要传出this时,应当让类继承enable_shared_from_this,查看一下这个类模板

1
template<class _Ty> class enable_shared_from_this

原来shared_from_this就是从weak_ptr创建一个shared_ptr,这个weak_ptr是创建控制块时通过_Resetp0 -> _Enable_shared -> _Do_enable得到的,而它实际上也是指向了控制块_Refptr
【Q】然后发现,这里会有一个weak_ptr,于是有以下问题:

  1. 这个指针是干嘛的?
    回顾一下问题,要在T里面搞出一个函数shared_from_this,返回指向自己的shared_ptr<T>
    解决方案很朴素,找个地方存一个shared_ptr<T>不就行了?考虑到没法逐个修改T本身,于是实现一个公共的enable_shared_from_this<T>来做这个事情。
  2. 为什么一定得是Weak的?
    因为上面说的存shared_ptr<T>实际上是不行的。存这玩意,实际上就是在T里面持有了一个shared_ptr<T>,这不循环引用了么,所以得用weak_ptr<T>
  3. shared_from_this如何通过weak_ptr<T>返回最终的shared_ptr<T>
    通过weak_ptr牵线搭桥,就可以直接创建一个shared_ptr了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template<class _Ty> class enable_shared_from_this
{ // provide member functions that create shared_ptr to this
public:
// 稍后我们将看到,这个_EStype被用来做SFINAE
typedef _Ty _EStype;

shared_ptr<_Ty> shared_from_this()
{
return (shared_ptr<_Ty>(_Wptr));
}

shared_ptr<const _Ty> shared_from_this() const
{
return (shared_ptr<const _Ty>(_Wptr));
}

protected:
constexpr enable_shared_from_this() _NOEXCEPT {}
enable_shared_from_this(const enable_shared_from_this&) _NOEXCEPT {}
enable_shared_from_this & operator=(const enable_shared_from_this&) _NOEXCEPT { return (*this); }
~enable_shared_from_this() _NOEXCEPT {}
...

函数_Do_enable是个自由函数,因为它用来沟通std::shared_ptr<T>std::enable_shared_from_this<T>这两个类。它接受三个参数,分别是托管对象的指针、enable_shared_from_this指针和控制块指针。由于托管对象继承了enable_shared_from_this,所以这1和2这两个指针其实是一样的,我们将看到在_Enable_shared函数中直接进行了强转。

1
2
3
4
5
...
private:
template<class _Ty1, class _Ty2> friend void _Do_enable(_Ty1 *, enable_shared_from_this<_Ty2>*, _Ref_count_base *);
weak_ptr<_Ty> _Wptr;
};

继续看上面提到的_Enable_shared函数,这里实际上是一个SFINAE,如果我们的类继承了enable_shared_from_this<T>,那么就会执行_Do_enable函数

1
2
3
4
5
6
7
8
9
10
11
template<class _Ty>
inline void _Enable_shared(_Ty *_Ptr, _Ref_count_base *_Refptr, typename _Ty::_EStype * = 0)
{ // reset internal weak pointer
if (_Ptr)
_Do_enable(_Ptr, (enable_shared_from_this<typename _Ty::_EStype>*)_Ptr, _Refptr);

}

inline void _Enable_shared(const volatile void *, const volatile void *)
{ // not derived from enable_shared_from_this; do nothing
}

下面查看这个关键的_Do_enable函数,实际上就是让weak_ptr指向对应的控制块。

1
2
3
4
5
template<class _Ty1, class _Ty2>
inline void _Do_enable(_Ty1 *_Ptr, enable_shared_from_this<_Ty2> *_Es, _Ref_count_base *_Refptr)
{
_Es->_Wptr._Resetw(_Ptr, _Refptr);
}

别名使用构造函数和owner_before

shared_ptr的定义中,有一个奇特的别名使用构造函数(aliasing constructor)。它管理一个指针r,但同时指向另外一个unrelated且unmanaged指针ptr。这个用法看似奇怪,但我们来考虑下面的两个问题:

  1. shared_ptr管理的对象,和指向的对象,是否一定要是同一个对象呢?
  2. 如何创建一个指向shared_ptr管理对象成员的shared_ptr

我们的答案是:

  1. 不一定。我们知道shared_ptr之间会共享一个引用计数块,表示自己管理的对象的生存周期,但是shared_ptr可能实际指向另一个对象。
  2. 可以,通过别名使用构造函数。
1
2
template<class Y> 
shared_ptr( const shared_ptr<Y>& r, element_type* ptr ) noexcept;

在下面的代码中,我们构造一个Father,它持有一个Son的实例,现在我们创建一个智能指针son,它持有father,但是却指向了&father->son。它负责管理father的生命周期,但调用get会返回son的指针。

1
2
3
4
5
6
7
8
9
10
11
struct Son { 
// some data that we want to point to
};

struct Father {
Son son;
};

std::shared_ptr<Father> father = std::make_shared<Father>(...);
// aliasing constructor用法
std::shared_ptr<Son> son(father, &father->son);

下面输出为2和2。

1
2
printf("%d\n", father.use_count());
printf("%d\n", son.use_count());

下面我们尝试通过shared_ptr::reset方法来释放father指针对其管理的Father对象的引用。

1
2
// 这时候Father对象的引用计数为2,我们不对Son来计算引用计数
father.reset();

下面的输出为0和1,以及1和0。

1
2
3
4
5
printf("%d\n", father.use_count());
printf("%d\n", son.use_count());

printf("%d\n", father.owner_before(son))
printf("%d\n", son.owner_before(father));

【Q】看到这里有个疑问,为什么father.use_count()就是0了,不是它给son做了alias constructor了么,怎么说也得是1啊,并且这个说明也展示了这一点。这里需要注意,在reset之后,这个shared_ptr就不指向实际的Father对象了,因此我们不能对它调用use_count。但为了验证它依然存在,我们可以跟踪Father的析构函数。并且,我们看到son的引用计数也因为father.reset()变成了1。

1
2
3
// 这时候Father对象仍然存在,并且引用计数为1
// 如果我们对Son对象计算引用计数的话,这个对象就会被销毁了
func(son);

因此可以发现,当我们需要将智能指针p指向一个对象father的某个字段,并且这个字段是一个依赖于该对象的智能指针的时候,我们需要使用aliasing constructor,从而保证当p不销毁时,father也一直存在。
此时如果使用operator<比较shared_ptr的大小关系就会发现它们不等,因为指向的对象不同。但此时应当owner_before用来比较两个shared_ptr之间的“大小关系”。

1
2
3
4
5
6
7
std::shared_ptr<Father> father = std::make_shared<Father>(Son());
std::shared_ptr<Son> son(father, &father->son);
printf("%d %d\n", father.owner_before(son), son.owner_before(father)); // 0 0

std::shared_ptr<Father> father2 = std::make_shared<Father>(Son());
std::shared_ptr<Son> son2 = std::make_shared<Son>();
printf("%d %d\n", father2.owner_before(son2), son2.owner_before(father2)); // 1 0

unique_ptr

unique_ptr实际上相当于一个安全性增强了的auto_ptr
容易想到,unique_ptr并不能被复制,所以它没有复制构造函数和复制赋值运算符。unique_ptr的使用标志着控制权的转移,如果有熟悉Rust的朋友应该会对此感触比较深了。
其实,一般如果实现一个简单的RAII,用unique_ptr也是可以的,毕竟它能handle住发生异常的情况。
同样,因为删除数组要用到delete [],所以对于数组有个偏特化版本。

weak_ptr

weak_ptr表示一个非所有性的访问。
可以通过expired检查自己持有的对象是否已经被删除。
可以通过lock将自己转换为一个shared_ptr

智能指针和容器联合使用

下面那种方式好呢?
我们知道emplace_back是接受一个prvalue,然后把它move进去。而push_back也可以接受一个右值,并move进去,所以1和2是没区别的。

1
2
3
my_vector.push_back(std::make_unique<Foo>("constructor", "args"));
my_vector.emplace_back(std::make_unique<Foo>("constructor", "args"));
my_vector.emplace_back(new Foo("constructor", "args"));

Reference

  1. https://zhuanlan.zhihu.com/p/47744606
    一个对别名构造函数的介绍。
  2. https://stackoverflow.com/questions/29089227/push-back-or-emplace-back-with-stdmake-unique
    对emplace_back/push_back unique_ptr的分析。
  3. http://blog.guorongfei.com/2017/01/25/enbale-shared-from-this-implementaion/
    对shared_from_this的讲解。