ARTICLE · 1182736
libc++ stl源码分析-揭秘智能指针shared_ptr(下)
libc++ stl源码分析-揭秘智能指针shared_ptr(下)这一篇主要来分析同智能指针shared_ptr相关的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中特定的宏,方便阅读和理解) 这个函数中涉及到libcxx标准库中的allocator分配器类模版,这一部分内容我们后续会单独用一篇内容讲解。这里我们只关注函数的模版参数和实现,可知: 函数,这个函数的实现如下 这个函数实现 我们这一篇文章主要关注如下几个点 5. 下面的语句用来返回真正的shared_ptr对象 也是本文的重点 __shared_ptr_emplace的源码实现如下(这里我省略一些本次分析中不需要的部分) 可知控制块中,会分配一块连续的内存用来存放 allocator分配器和shared_ptr所管理的对象的内存。并且通过位置new的方式来进行初始化。 也就是:先析构 allocator 存储,再用重绑定到控制块类型的 allocator 把整块内存 deallocate 掉。对象、allocator、控制块三者其实在同一块内存上,所以释放必须整块释放。 该函数的实现如下 这是 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的实现。
为啥make_shared内存分配效率更高 什么场景下无法使用make_shared
make_shared源码分析


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)...);}
C++17中 make_shared不支持数组类型 make_shared接受一个类型和一个变参的模版参数 用来初始化对应的shared_ptr make_shared最终会委托给
std::allocate_sharedtemplate <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));}
_ControlBlock的实现,本质上是一个__shared_ptr_emplace对象 _ControlBlockAllocator 控制块分配器,通过__allocator_traits_rebind获取,关于__allocator_traits_rebind的使用和作用 会留在后续文章讲解 这篇文章不赘述 __allocation_guard<_ControlBlockAllocator> __guard(__a, 1); 这一块的实现 暂时不讲 后续单独讲解 现在只需要知道 这个语句的作用是分配一块空间出来 这块空间就是控制块所占用的空间 接下来的new语句(位置new)用来初始化控制块 这一部分内容是本篇文章的重点
::new ((void*)std::addressof(*__guard.__get())) _ControlBlock(__a, std::forward<_Args>(__args)...);shared_ptr<_Tp>::__create_with_control_block控制块源码讲解
// 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_;};
控制块具体布局方式,由_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);}
那个 __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;}















































































