夜雨聆风学习资料网

ARTICLE · 1182736

libc++ stl源码分析-揭秘智能指针shared_ptr(下)

libc++ stl源码分析-揭秘智能指针shared_ptr(下)
这一篇主要来分析同智能指针shared_ptr相关的make_shared的具体实现。主要回答如下问题
  • 为啥make_shared内存分配效率更高
  • 什么场景下无法使用make_shared

make_shared源码分析

在libc++ stl源码分析-揭秘智能指针shared_ptr(中)文章中,讲解了通过make_shared创建的shared_ptr具备如下内存布局
并且make_shared所创建的shared_ptr的控制块是一个__shared_ptr_emplace类模版的实例化。__shared_ptr_emplace的一个简单布局如下
下面我们来深入分析make_shared的源码 进一步理解上述内容。
make_shared的源码如下(这里我去掉libcxx中特定的宏,方便阅读和理解)
template <class _Tp, class... _Args, __enable_if_t<!is_array<_Tp>::value, int> = 0>shared_ptr<_Tp> make_shared(_Args&&... __args) {  return std::allocate_shared<_Tp>(allocator<__remove_cv_t<_Tp> >(), std::forward<_Args>(__args)...);}
这个函数中涉及到libcxx标准库中的allocator分配器类模版,这一部分内容我们后续会单独用一篇内容讲解。这里我们只关注函数的模版参数和实现,可知:
  1. C++17中 make_shared不支持数组类型
  2. make_shared接受一个类型和一个变参的模版参数 用来初始化对应的shared_ptr
  3. make_shared最终会委托给
std::allocate_shared
函数,这个函数的实现如下
template <class _Tp, class _Alloc, class... _Args, __enable_if_t<!is_array<_Tp>::value, int> = 0>shared_ptr<_Tp> allocate_shared(const _Alloc& __a, _Args&&... __args) {  using _ControlBlock          = __shared_ptr_emplace<_Tp, _Alloc>;  using _ControlBlockAllocator = typename __allocator_traits_rebind<_Alloc, _ControlBlock>::type;  __allocation_guard<_ControlBlockAllocator> __guard(__a, 1);  ::new ((void*)std::addressof(*__guard.__get())) _ControlBlock(__a, std::forward<_Args>(__args)...);  auto __control_block = __guard.__release_ptr();  return shared_ptr<_Tp>::__create_with_control_block(      (*__control_block).__get_elem(), std::addressof(*__control_block));}
这个函数实现 我们这一篇文章主要关注如下几个点
  1. _ControlBlock的实现,本质上是一个__shared_ptr_emplace对象
  2. _ControlBlockAllocator 控制块分配器,通过__allocator_traits_rebind获取,关于__allocator_traits_rebind的使用和作用 会留在后续文章讲解 这篇文章不赘述
  3. __allocation_guard<_ControlBlockAllocator> __guard(__a, 1); 这一块的实现 暂时不讲 后续单独讲解 现在只需要知道 这个语句的作用是分配一块空间出来 这块空间就是控制块所占用的空间
  4. 接下来的new语句(位置new)用来初始化控制块 这一部分内容是本篇文章的重点
::new ((void*)std::addressof(*__guard.__get())) _ControlBlock(__a, std::forward<_Args>(__args)...);
5. 下面的语句用来返回真正的shared_ptr对象 也是本文的重点
shared_ptr<_Tp>::__create_with_control_block

控制块源码讲解

__shared_ptr_emplace的源码实现如下(这里我省略一些本次分析中不需要的部分)
// This tag is used to instantiate an allocator type. The various shared_ptr control blocks// detect that the allocator has been instantiated for this type and perform alternative// initialization/destruction based on that.struct __for_overwrite_tag {};template <class _Tp, class _Alloc>struct __shared_ptr_emplace : __shared_weak_count {  using __value_type  = __remove_cv_t<_Tp>; // 省略的构造函数  template <class... _Args,            class _Allocator                                                                          = _Alloc,            __enable_if_t<!is_same<typename _Allocator::value_type, __for_overwrite_tag>::value, int> = 0>  explicit __shared_ptr_emplace(_Alloc __a, _Args&&... __args) : __storage_(std::move(__a)) {    using _TpAlloc = typename __allocator_traits_rebind<_Alloc, __value_type>::type;    _TpAlloc __tmp(*__get_alloc());    allocator_traits<_TpAlloc>::construct(__tmp, __get_elem(), std::forward<_Args>(__args)...);  }_Alloc* __get_alloc() _NOEXCEPT { return __storage_.__get_alloc(); }__value_type* __get_elem() _NOEXCEPT { return __storage_.__get_elem(); }private:  template <class _Allocator                                                                         = _Alloc,            __enable_if_t<is_same<typename _Allocator::value_type, __for_overwrite_tag>::value, int> = 0> void __on_zero_shared_impl() _NOEXCEPT {    __get_elem()->~__value_type();  }  template <class _Allocator                                                                          = _Alloc,            __enable_if_t<!is_same<typename _Allocator::value_type, __for_overwrite_tag>::value, int> = 0>void __on_zero_shared_impl() _NOEXCEPT {    using _TpAlloc = typename __allocator_traits_rebind<_Allocator, __remove_cv_t<_Tp> >::type;    _TpAlloc __tmp(*__get_alloc());    allocator_traits<_TpAlloc>::destroy(__tmp, __get_elem());  }void __on_zero_shared() _NOEXCEPT override { __on_zero_shared_impl(); }void __on_zero_shared_weak() _NOEXCEPT override {    using _ControlBlockAlloc   = typename __allocator_traits_rebind<_Alloc, __shared_ptr_emplace>::type;    using _ControlBlockPointer = typename allocator_traits<_ControlBlockAlloc>::pointer;    _ControlBlockAlloc __tmp(*__get_alloc());    __storage_.~_Storage();    allocator_traits<_ControlBlockAlloc>::deallocate(__tmp, pointer_traits<_ControlBlockPointer>::pointer_to(*this), 1);  }  // TODO: It should be possible to refactor this to remove `_Storage` entirely.  // This class implements the control block for non-array shared pointers created  // through `std::allocate_shared` and `std::make_shared`.  struct _Storage {    struct _Data {      _LIBCPP_COMPRESSED_PAIR(_Alloc, __alloc_, __value_type, __elem_);    };    _ALIGNAS_TYPE(_Data) char __buffer_[sizeof(_Data)];    explicit _Storage(_Alloc&& __a) { ::new ((void*)__get_alloc()) _Alloc(std::move(__a)); }    ~_Storage() { __get_alloc()->~_Alloc(); }    _Alloc* __get_alloc() _NOEXCEPT {      return std::addressof(reinterpret_cast<_Data*>(__buffer_)->__alloc_);    }    __value_type* __get_elem() _NOEXCEPT {      return std::addressof(reinterpret_cast<_Data*>(__buffer_)->__elem_);    }  };  _Storage __storage_;};
可知控制块中,会分配一块连续的内存用来存放 allocator分配器和shared_ptr所管理的对象的内存。并且通过位置new的方式来进行初始化。

控制块具体布局方式,由_Storage来决定。

控制块自身的释放,由__on_zero_shared_weak 控制:

void __on_zero_shared_weak() _NOEXCEPT override {    using _ControlBlockAlloc   = typename __allocator_traits_rebind<_Alloc, __shared_ptr_emplace>::type;    using _ControlBlockPointer = typename allocator_traits<_ControlBlockAlloc>::pointer;    _ControlBlockAlloc __tmp(*__get_alloc());    __storage_.~_Storage();    allocator_traits<_ControlBlockAlloc>::deallocate(__tmp, pointer_traits<_ControlBlockPointer>::pointer_to(*this), 1);  }
也就是:先析构 allocator 存储,再用重绑定到控制块类型的 allocator 把整块内存 deallocate 掉。对象、allocator、控制块三者其实在同一块内存上,所以释放必须整块释放。

那个 __for_overwrite_tag 是干嘛的

注释里说得很直白:

This tag is used to instantiate an allocator type. The various shared_ptr control blocks detect that the allocator has been instantiated for this type and perform alternative initialization/destruction based on that.

当你写allocate_shared(alloc, ...)时,libc++ 有时会构造一个"假的 allocator",它的value_type被设成__for_overwrite_tag,用来走一条特殊路径(比如对象是平凡可析构的、可以跳过destroy直接覆盖)。代码里就是靠is_same这个 SFINAE 条件,在两个重载之间二选一:

是__for_overwrite_tag:__on_zero_shared_impl里直接__get_elem()->~__value_type();

不是:走allocator_traits::destroy,尊重用户 allocator 的析构语义。

构造函数那边也用同样的条件做了重载选择。

__create_with_control_block 源码分析

该函数的实现如下
  template <class_Yp, class_CntrlBlk>  static shared_ptr<_Tp>__create_with_control_block(_Yp* __p, _CntrlBlk* __cntrl) _NOEXCEPT {    shared_ptr<_Tp> __r;    __r.__ptr_   = __p;    __r.__cntrl_ = __cntrl;    __r.__enable_weak_this(__r.__ptr_, __r.__ptr_);    return __r;  }
这是 libc++(LLVM 的 C++ 标准库实现)里std::shared_ptr的一个内部辅助函数。它的作用是:用已经准备好的对象指针和控制块指针,直接拼装出一个shared_ptr,不走常规构造流程(不增加引用计数)。
关键点:
默认构造出的shared_ptr引用计数是空的(两个成员都是 nullptr)。正常情况下,构造一个管理对象的shared_ptr会去创建/接管控制块,并把use_count置为 1。而这里跳过了所有引用计数操作,直接手动把两个成员填进去。
因为是类自己的静态成员函数,它可以访问__r的私有成员__ptr_和__cntrl_。
__enable_weak_this是给enable_shared_from_this用的:如果_Yp继承自enable_shared_from_this,就把它的内部weak_ptr指向这个对象,这样以后调用shared_from_this()才能拿到有效的shared_ptr。参数传两次__r.__ptr_(一个是_Yp*,一个是用于比较的const volatile void*之类),是为了处理多重继承/指针调整的场景。
按值返回__r。这里通常依赖 RVO/NRVO(返回值优化),编译器会直接在调用方的位置构造,不会多一次拷贝。
它和普通构造的区别 —— 为什么不直接 shared_ptr(__p)?
常规的shared_ptr(p)构造会:
检查p能否赋给T*;
如果p是从enable_shared_from_this来的裸指针且已有控制块,会做特殊处理;
新建一个控制块,把use_count设为 1。
而__create_with_control_block的前提是:控制块已经存在,并且已经被正确初始化好了(引用计数已经按需要设置好,通常是 1)。它只做"绑定"这一件事,不做计数、不 new、不抛异常。
所以它被用在这样的场景里:某个函数先手动分配好对象和控制块(比如allocate_shared、make_shared、__shared_ptr_emplace的构造路径,或者weak_ptr::lock()),然后调用这个函数把两者组装成一个shared_ptr返回。这样能:
避免二次分配;
避免多余的原子引用计数操作;
保证_NOEXCEPT。
总结
到此,我们讲解完了整个的shared_ptr的实现,后续我们会继续分析std::vector的实现。

相关学习资料