本文主要讲述c++提供的 shared_ptr
、weak_ptr
,make_shared
和 enable_shared_from_this
,以及详解MSVC中STL提供的智能指针源码。
具体使用方法这里不会从头开始介绍,可以参考这篇文章:深度学习可好玩了:c++:RAII与智能指针
原理
有关智能指针的实现,我认为主要核心在于以下内容:
最后再介绍 make_shared
和 enable_shared_from_this
的原理。
如何破除循环引用
首先c++不会像python一样提供“部分标记-清除算法”来检测并破除循环引用,这对性能的影响太大。
我们再编写代码时,应该极力避免循环引用的情况。但是在订阅者模式或者观察者模式中,循环引用也是不可避免的,这时c++打出 shared_ptr
和 weak_ptr
这套组合拳。weak_ptr
本身不控制资源,只是一个弱引用。
所以c++的智能指针其实并不支持两个引用都想掌控资源。但当一个掌握资源,另一个只是观察者即weak_ptr时,c++智能指针可以支持。
何时引用计数改变
为了破除循环引用,需要同时支持两个智能指针 shared_ptr
和 weak_ptr
,所以引用计数也要有两个:
- uses强引用,记录被多少个shared_ptr掌握;
- weaks弱引用,记录被多少个weak_ptr掌握,再+1;
uses何时改变:
shared_ptr
构造时,+1;shared_ptr
拷贝赋值时,+1;weak_ptr.lock()
,发生weak到shared的晋级,+1;shared_ptr
析构时,-1;
weaks何时改变:
weak_ptr
构造时,+1;weak_ptr
拷贝赋值时,+1;weak_ptr
析构时,-1;- 当uses减为0时,weaks-1;
我们会发现weaks,在初始化时会多加一个1,所以当uses减为0时,weaks再-1。这样就平衡了。之所以加1是为了保持接口的一致性。
两个引用大体上是独立的,除了 weak_ptr.lock()
这个特例,发生了晋级。
何时释放内存资源
我们知道一个智能指针的实现最起码包括:指针(指向资源)、强引用、弱引用这三个数据成员。
所以我们需要释放两块内存,分别是数据块和控制块。
- 由于弱引用是不控制数据块资源的,所以是否释放数据块仅需观察强引用uses,当uses==0时,释放数据块;
- 至于控制块的释放,自然是要等uses
==
0且weaks也==
0时,还记得吗,在分析引用计数何时改变时,介绍到weaks初始化会多加1,当uses==
0时,再--。因此当weaks==
0时,uses必==
0,所以只需判断weaks是否==
0即可。
通过上面的分析,得出一个注意事项,那就是weak_ptr在使用前很可能uses==0,导致数据块已经被释放了,因此weak_ptr并不控制数据块的生命周期。
如何保障线程安全
线程安全的智能指针是进阶知识,智能指针的线程安全只保障到引用计数,也就是说引用计数的变化是线程安全的,但智能指针中的指针是不安全的,我们可以举一个很简单的例子。
// global
std::shared_ptr<Foo> g_ptr;
// 线程1
std::shared_ptr<Foo> x;
x = g_ptr;
// 线程2
std::shared_ptr<Foo> y;
g_ptr = y
线程1在赋值操作之前,很有可能g_ptr已经被更改了。
因此我们可以说出智能指针中很经典的一句话:
- 多个线程同时读写多个(值传递)
shared_ptr
实例是线程安全的; - 多个线程同时读写一个
shared_ptr
实例是非线程安全的;
在多线程编程中,我们应该用值传递构造一个新的share_ptr,这样就确保是安全的,引用计数本身就是安全的,无需care,而值传递又确保了智能指针中的指针也是安全的。
注意:上面都是介绍shared_ptr自身的线程安全性,并不是shared_ptr管理资源的安全性。
讲完shared_ptr的多线程安全性,我们来看看它是怎么实现引用计数的线程安全,主要有两个手段:
- 引用计数的加减都是原子操作
weak_ptr.lock()
通过CAS(Compare and Swap)操作实现自旋锁
第一个点很简单不介绍了,我们主要讲第二个点。
lock是weak_ptr获得shared_ptr的权限,发生了weak到shared的晋级,在lock之前需要判断uses是否 ==
0;如果uses ==
0,那么lock将失败,因为这时资源不再有效。
为了实现线程安全,我们需要在lock前判断是否有别的线程更改了uses,并且要求uses!=0。这里采用while循环的自旋锁是个好主意,不断判断条件,满足条件后uses+1,当然这还不够,还需要保证 从判断条件到uses+1
这段代码是原子操作,即不可中断。这就引入了CAS(Compare and Swap),是各个cpu都支持的、从硬件上实现的原子操作。
如windows提供的API_InterlockedCompareExchange(当前值,新值,期望值)
若当前值和期望值相同,则当前值更新为新值;
若当前值和期望值不相同,则什么也不做;
返回第一个参数的原始值。
下面我们来看一下完整代码,lock最终会调用_Incref_nz
函数。
bool _Incref_nz() noexcept {
/*
该方法是后续weak_ptr.lock()线程安全的基石,
weak_ptr.lock()会把weak_ptr晋级为shared_ptr,而在晋级过程中,如果uses已经降为0,还将发生不预期的行为
所以在晋级过程中,必须保证uses始终不为0。这里采用CAS操作(原子操作不可中断)
本质上这段代码实现自旋锁,一种不阻塞的锁,cpu执行会一直循环
*/
auto& _Volatile_uses = reinterpret_cast<volatile long&>(_Uses);
// 强制转换为volatile&,为了限制编译器对其进行优化,volatile使得每次读取_Uses时,必须从内存中重新读取
long _Count = _ISO_VOLATILE_LOAD32(_Volatile_uses); // 创建新副本
while (_Count != 0) {
// while所以这段代码会一直循环,实现自旋锁
// 当_Count==0,说明uses置0了,将会lock失败
const long _Old_value =
_INTRIN_RELAXED(_InterlockedCompareExchange)(&_Volatile_uses, _Count + 1, _Count);
if (_Old_value == _Count) { // +1成功
return true;
}
_Count = _Old_value; // +1失败,更新期望值_Count
}
return false;
}
上面代码中,1要保证++是原子操作,2要保证_Uses不为0。
_Count是_Uses的副本,就是期望值,若+1成功直接返回;若+1失败,要更新期望值_Count(即第21 line)。
这里通过判断期望值确定是否为0。
enable_shared_from_this
从名字可以看出来enable_shared_from_this,是为了让shared_ptr支持this指针,这在回调中比较常见。
如果直接用shared_ptr管理this指针,会导致多次delete释放资源。正确的做法是继承于enable_shared_from_this。
先看错误案例:
class Book {
public:
std::shared_ptr<Book> getptr() {
return std::shared_ptr<Book>(this);
}
};
Book *data = new Book(3);
std::shared_ptr<Book> p1 = data->getptr();
std::shared_ptr<Book> p2 = data->getptr();
此时p1和p2都控制data,但它们的引用计数是独立的,因为每次调用getptr() return的都是一个新的shared_ptr,具有新的引用计数。这就导致了p1和p2各释放一次内存。
来看正确做法:
struct Book: std::enable_shared_from_this<Book>
{
std::shared_ptr<Book> getptr() {
return shared_from_this();
}
};
auto data = std::make_shared<Book>(); // 这一步非常重要
std::shared_ptr<Book> p1 = data.getptr();
std::shared_ptr<Book> p2 = data.getptr();
我们发现Book继承了enable_shared_from_this,然后再调用shared_from_this方法。
原理其实很简单enable_shared_from_this这个基类具有一个数据成员 _Wptr
(weak_ptr类型),每次shared_from_this都是拷贝构造内部的 _Wptr
,这样它们的引用计数是共享的,不是独立的。
重点:第7行代码 auto data = std::make_shared<Book>();
很重要。想要正确的使用 enable_shared_from_this
,就请先构造一个shared_ptr。不可以是构造一个unique_ptr,更不可以是栈上的变量如 Book data;
。
这有两方面原因:
- 栈上变量,绝不能用智能指针控制,智能指针只能控制堆上变量。因为栈会自动析构,如果用智能指针包装,会导致多次析构;
- enable_shared_from_this内部的weak_ptr是在第7行才初始化,即构造shared_ptr时,这很有意思,也就是说内部的_Wptr并不是在enable_shared_from_this构造函数中初始化。原因是如果不用到
_Wptr
,那么没必要初始化以节省开销。到源码再仔细介绍。
总之在使用时,我们记住就行,要想要正确的使用 enable_shared_from_this
,就请先构造一个shared_ptr。
make_shared
make_shared是构造shared_ptr,它相对于 shared_ptr<T>(new T)
有两个好处:
- 开销更小,make_shared只需内存分配一次;
- 是异常安全的;
对于第一个点make_shared只需内存分配一次
我们知道内存分配是一件比较麻烦的事情,找连续内存很耗时,而且会导致内存碎片。能一次性一块分配了就不要分成两次。
对于第二个点异常安全,参考《effective c++》
来看一个例子:
processWidget(std::shared_ptr<Widget>(new Widget), funct());
该函数有3部分 std::shared_ptr<Widget>构造函数
,new Widget
动态分配,funct()
。
c++对它们3个的执行顺序是不确定的,这就意味着,有可能是以下顺序执行的:
new Widget
动态分配;funct()
;std::shared_ptr<Widget>构造函数
;
此时如果funct()抛出异常,那么就会发生内存泄漏!解决方法有两个:一、用make_shared,因为一次性分配不存在分步执行;二、将new用独立语句执行,比如:
std::shared_ptr<Widget> pw(new Widget)
processWidget(pw, funct());
那make_shared这么好,有什么缺点呢?有一个延迟释放资源的缺点,因为make_shared是将数据块和控制块一起分配内存,好兄弟同生同死,所以也要一起释放。那么即便_Uses减为0了,_Weaks不为0,数据块资源依旧无法释放。直到_Weaks为0。
源码
本文源码参考来自MSVC,github地址。
其实一开始并不建议直接看STL中的源码,因为STL是模板编程,要考虑很多情况。在看STL源码之前,可以先看简单的智能指针实现,对其中的七大件:构造\拷贝构造\移动构造\析构\赋值\拷贝赋值\移动赋值,做到心知肚明。
在看STL源码之前最好对:copy and swap惯用法、模板元编程(类型萃取,SFINAE,enable_if_t,void_t等),c++11特性(=default、=delete、using用法、就地初始化、noexcept用法、移动语义、完美转发等)有所了解,当然边看边学也是个好主意。
智能指针整体的类图见下,可以发现 share_ptr
和 weak_ptr
只是对外提供一些api,主要都是由 Ptr_base
实现,尤其是这6个构造函数,其中 Ptr_base
的 _rep
数据成员很重要,它是一个 Ref_count_base
指针,指向控制块。值得注意的是 _rep
必须是指针,这样可以通过指针调用虚函数来实现 _Delete_this
和 _Destroy
多态。
控制块的数据成员有:指向数据块的指针、两个引用计数。函数成员有:引用计数的加减、释放数据块资源、释放控制块自身资源等。控制块有三种类型:Ref_count
、Ref_count_del
、Ref_count_del_alloc
,看名字就知道它们分别支持单独的指针、指针+自定义删除器、指针+自定义删除器+分配器。它们都有一个共同的基类 Ref_count_base
。
控制块
下面我们先看控制块部分的代码:
主要关心 _Destory()
和 _Delete_this()
这两个虚函数:
- _Destory() 释放指针指向资源;
- _Delete_this()释放控制块自身的资源;
此外 _Uses
和 _Weaks
都是原子操作的加减,确保引用计数是线程安全的。至于_Incref_nz实现的自旋锁也是线程安全的基石,之前讲过,这里就不讲了。
具体代码:
// 控制块的抽象基类
class Ref_count_base {
private:
// c++11就地初始化
std::_Atomic_counter_t _Uses = 1;
std::_Atomic_counter_t _Weaks = 1; // 在初始化时多加1
// 虚函数多态特性,不同派生类有不同的表现
virtual void _Destory() noexcept = 0; // 释放ptr指向资源
virtual void _Delete_this() noexcept = 0; // 释放控制块自身的资源
public:
virtual ~Ref_count_base() noexcept {};
Ref_count_base() = default;
Ref_count_base(const Ref_count_base&) = delete;
Ref_count_base& operator=(const Ref_count_base&) = delete;
bool _Incref_nz() noexcept {
auto& _Volatile_uses = reinterpret_cast<volatile long&>(_Uses);
long _Count = _Volatile_uses;
while (_Count != 0) {
const long _Old_value = _INTRIN_RELAXED(_InterlockedCompareExchange)(&_Volatile_uses, _Count + 1, _Count);
if (_Old_value == _Count) {
return true;
}
_Count = _Old_value;
}
return false;
}
void _Incwref() noexcept {
_MT_INCR(_Weaks); //原子操作
}
void _Decwref() noexcept {
if (_MT_DECR(_Weaks) == 0) {
_Delete_this();
}
}
void _Incref() noexcept {
_MT_INCR(_Uses);
}
void _Decref() noexcept { //
if (_MT_DECR(_Uses) == 0) {
// 当uses减为0时,那么释放ptr的资源,并且weak--,因为在初始化时weak多加了1
_Destory();
_Decwref();
}
}
long _Use_count() const noexcept {
return static_cast<long>(_Uses);
}
};
template <class Ty>
class Ref_count : public Ref_count_base {
private:
Ty* _ptr;
virtual void _Destroy() noexcept override {
delete _ptr;
}
virtual void _Delete_this() noexcept override {
delete this;
}
public:
explicit Ref_count(Ty* ptr) :_ptr(ptr) {}
};
template <class Ty,class Dy>
class Ref_count_del : public Ref_count_base {
private:
Ty* _ptr;
Dy _dtor;
virtual void _Destroy() noexcept override {
_dtor(_ptr); // 调用自定义的deleter
}
virtual void _Delete_this() noexcept override {
delete this;
}
public:
Ref_count_del(Ty* ptr, Dx dtor): _ptr(ptr), _dtor(dtor){}
};
// TODO 支持Ref_count_del_alloc
Ptr_base
Ptr_base实现了足足有六个construct函数,这些函数将在shared_ptr和weak_ptr构造时调用。
可以发现 _Construct_from_weak
构造函数调用了 _Incref_nz
实现线程安全的晋级。
还有一类比较特殊的 _Alias_construct_from
别名构造函数,它给我们提供了部分对象管理的功能,详见文章。
在编写构造函数时,我们主要关心引用计数的变化。
具体代码:
template <class Ty>
class Ptr_base {
public:
Ptr_base() = default;
~Ptr_base() = default;
using element_type = std::remove_extent_t<Ty>;
long use_count() const noexcept {
return _rep ? _rep->_Use_count() : 0 ;
}
element_type* get() const noexcept { return _ptr; }
protected:
template <class Ty2>
void _Move_construct_from(Ptr_base<Ty2>&& other) noexcept {
_ptr = other._ptr;
_rep = other._rep;
other._ptr = nullptr;
other._rep = nullptr;
}
template <class Ty2>
void _Copy_construct_from(const Ptr_base<Ty2>& other) noexcept {
if (other._rep) {
other._rep->Incref();
}
_ptr = other._ptr;
_rep = other._rep;
}
template <class Ty2>
void _Alias_construct_from(const Ptr_base<Ty2>& other, element_type* ptr) noexcept {
if (other._rep) {
other._rep->Incref();
}
_ptr = _ptr;
_rep = other._rep;
}
template <class Ty2>
void _Alias_move_construct_from(Ptr_base<Ty2>&& other, element_type* ptr) noexcept {
_ptr = _ptr;
_rep = other._rep;
other._ptr = nullptr;
other._rep = nullptr;
}
// weak->shared
template <class Ty2>
bool _Construct_from_weak(const Ptr_base<Ty2>& other) noexcept {
if (other._rep && other._rep->_Incref_nz()) { // 要确保_rep有效,且uses!=0
_ptr = other._ptr;
_rep = other._rep;
return true;
}
return false;
}
// shared->weak
template <class Ty2>
void _Weakly_construct_from(const Ptr_base<Ty2>& other) noexcept {
if (other._Rep) {
_ptr = other._ptr;
_rep = other._rep;
_rep->_Incwref();
}
}
void _Swap(Ptr_base& other){
using namespace std;
std::swap(_ptr, other._ptr);
std::swap(_rep, other._rep);
}
void _Decwref() noexcept {
if (_rep) {
_rep->_Decwref();
}
}
void _Decref() noexcept {
if (_rep) {
_rep->_Decref();
}
}
private:
element_type* _ptr = nullptr;
Ref_count_base* _rep = nullptr;
};
类型萃取
因为STL是模板编程吗,类型萃取是不可避免的,这里罗列一下主要用到的类型萃取的模板类,完全可以跳过!我们主要是研究智能指针。
template <class... _Types>
using void_t = void; // void_t技巧,常用于类型检查
// 是否可以delete p;
template <class Ty, class = void>
struct Can_scalar_delete : std::false_type {};
template <class Ty>
struct Can_scalar_delete<Ty, void_t<decltype(delete std::declval<Ty*>())>> : std::true_type {};
// 是否可以delete[] p;
template <class Ty, class = void>
struct Can_array_delete : std::false_type {};
template <class Ty>
struct Can_array_delete<Ty, void_t<decltype(delete[] std::declval<Ty*>())>> : std::true_type {};
//
template <class Ty, class _Ty>
struct SP_convertible : std::is_convertible<Ty*, _Ty*>::type {};
template <class Ty, class Uty>
struct SP_convertible<Ty, Uty[]> : std::is_convertible<Ty(*)[], Uty(*)[]>::type {};
template <class Ty, class Uty, size_t Ext>
struct SP_convertible<Ty, Uty[Ext]> : std::is_convertible<Ty(*)[Ext], Uty(*)[Ext]>::type {};
// 这在传递自定义的删除器时用于检测参数是否callable
template <class Fx, class Arg, class = void>
struct Can_call_function_object : std::false_type {};
template <class Fx, class Arg>
struct Can_call_function_object<Fx, Arg, void_t<decltype(std::declval<Fx>()(std::declval<Arg>()))>> : std::true_type {};
// 两个Type是否是兼容的,这当赋值函数的两个type不同时用到,要确保两个type是兼容的
template <class Yty, class Ty>
struct SP_pointer_compatible : std::is_convertible<Yty*, Ty*>::type {};
template <class Uty, size_t Ext>
struct SP_pointer_compatible<Uty[Ext], Uty[]> : std::true_type {};
template <class Uty, size_t Ext>
struct SP_pointer_compatible<Uty[Ext], const Uty[]> : std::true_type {};
template <class Uty, size_t Ext>
struct SP_pointer_compatible<Uty[Ext], volatile Uty[]> : std::true_type {};
template <class Uty, size_t Ext>
struct SP_pointer_compatible<Uty[Ext], const volatile Uty[]> : std::true_type {};
// 是否继承于enable_shared_from_this,这在shared_ptr构造函数时用到
// 主要是看有没有定义_Esft_type类型
template <class _Yty, class = void>
struct Can_enable_shared : std::false_type {};
template <class _Yty>
struct Can_enable_shared<_Yty, void_t<typename _Yty::_Esft_type>>
: std::is_convertible<std::remove_cv_t<_Yty>*, typename _Yty::_Esft_type*>::type {};
using std::is_array_v;
using std::negation;
using std::is_volatile;
shared_ptr
shared_ptr 继承于Ptr_base,只是支持对外的一些API。
本文代码是支持数组的,在c++17之前,如果传递的是数组,需要自定义删除器,比如
std::shared_ptr<int>ptr(new int[10],std::default_delete<int[]>());
在c++17之后,shared_ptr支持了数组,无需自定义删除器,原理是在构造函数时,通过is_array_v类型萃取实现的。
整个代码用了大量类型萃取,可以忽略,不影响shared_ptr的本质。
主要是注意构造函数调用的 _Set_ptr_rep_and_enable_shared
方法,这可能让很多人看的一头雾水,Set_ptr_rep可以理解,就是设置ptr,并且new一个控制块,但enable_shared是啥意思?这必须要看到 enable_shared_from_this
类,才能讲明白。总之 _Set_ptr_rep_and_enable_shared
会判断如果“构造函数传进来的指针”指向的是一个 enable_shared_from_this
类,那么它会初始化这个类的 _Wptr
指针!再次回忆起,在讲解原理时我们就提到 enable_shared_from_this
是在构造shared_ptr时才初始化内部的 _Wptr
!
_Set_ptr_rep_and_enable_shared需要反复看,联系起enable_shared_from_this代码和用法。
具体代码:
template <class Ty>
class shared_ptr : public Ptr_base<Ty> {
private:
using Mybase = Ptr_base<Ty>;
public:
using typename Mybase::element_type;
// 构造函数
shared_ptr() noexcept {}
shared_ptr(nullptr_t) noexcept :_ptr(nullptr_t),_rep(nullptr_t) {}
template <class Ux,
typename = std::enable_if_t<std::conjunction_v<
is_array_v<Ty>,Can_array_delete<Ux>,Can_scalar_delete<Ux>,SP_convertible<Ux,Ty> >
>>
explicit shared_ptr(Ux* ptr){
if constexpr (is_array_v<Ty>) { // c++17后开始支持数组
_Setpd(ptr, default_delete<Ux[]>{});
}
else {
_Set_ptr_rep_and_enable_shared(ptr, new Ref_count<Ux>(ptr));
}
}
template <class Ux, class Dx,
typename = std::enable_if_t<std::conjunction_v<
is_move_constructible<Dx>,Can_call_function_object<Dx&, Ux*&>,SP_convertible<Ux, Ty> >
>>
shared_ptr(Ux* ptr,Dx dtor) {
_Setpd(ptr, std::move(dtor));
}
template <class Dx,
typename = std::enable_if_t<std::conjunction_v<
is_move_constructible<Dx>, Can_call_function_object<Dx&, nullptr_t&>>
>>
shared_ptr(nullptr_t, _Dx _Dt) {
_Setpd(nullptr, std::move(dtor));
}
// TODO 支持alloc参数
template <class Ty2> // alias construction 并不要求Ty2和Ty兼容,因为alias应用场景是类和成员
shared_ptr(const shared_ptr<Ty2>& other, element_type* ptr) noexcept {
this->_Alias_construct_from(other, ptr);
}
template <class Ty2>
shared_ptr(shared_ptr<Ty2>&& other, element_type* ptr) noexcept {
this->_Alias_move_construct_from(std::move(other), ptr);
}
shared_ptr(const shared_ptr& other) noexcept {
this->_Copy_construct_from(other);
}
template <class Ty2 // 要求Ty2和Ty兼容
typename = std::enable_if_t<SP_pointer_compatible<Ty2, Ty>::value>>
shared_ptr(const shared_ptr<Ty2>& other) noexcept {
this->_Copy_construct_from(other);
}
shared_ptr(shared_ptr&& other) {
this->_Move_construct_from(std::move(other));
}
template <class Ty2,
typename = std::enable_if_t<SP_pointer_compatible<Ty2, Ty>::value>>
shared_ptr(shared_ptr<Ty2>&& other) {
this->_Move_construct_from(std::move(other));
}
// 从weak晋级到shared,要确保uses不等于0,_Construct_from_weak要安全
template <class Ty2,
typename = std::enable_if_t<SP_pointer_compatible<Ty2, Ty>::value>>
explicit shared_ptr(const weak_ptr<Ty2>& other) {
if (!this->_Construct_from_weak(other)) {
throw exception("bad weak ptr");
}
}
// 析构函数,引用计数--
~shared_ptr() noexcept {
this->_Decref();
}
void swap(shared_ptr& _Other) noexcept {
this->_Swap(_Other);
}
// 赋值函数 copy and swap技巧
shared_ptr& operator=(shared_ptr tmp) noexcept {
tmp.swap(*this);
return *this;
}
template <class Ty2>
shared_ptr& operator=(shared_ptr<Ty2> tmp) noexcept {
tmp.swap(*this);
return *this;
}
// 释放指针,并置为空,巧妙用swap代码复用
void reset() noexcept {
shared_ptr().swap(*this);
}
// 释放指针,并置为ptr
template <class Ux>
void reset(Ux* ptr) {
shared_ptr(ptr).swap(*this);
}
template <class Ux, class Dx>
void reset(Ux*ptr, Dx dtor) {
shared_ptr(ptr, dtor).swap(*this);
}
using Mybase::get;
// 取指针操作
element_type& operator*() const noexcept {
return *get();
}
element_type* operator->() const noexcept {
return get();
}
template <typename = std::enable_if_t<is_array_v<Ty>>>
element_type& operator[](ptrdiff_t _Idx) const noexcept {
return get()[_Idx];
}
bool unique() const noexcept {
return this->use_count() == 1;
}
explicit operator bool() const noexcept {
return get() != nullptr;
}
private:
template <class Uxptr, class Dx>
void _Setpd(const Uxptr ptr, Dx dtor) {
_Set_ptr_rep_and_enable_shared(ptr, new Ref_count_del<Uxptr, Dx>(ptr, std::move(dtor)));
}
// TODO支持 alloc参数 _Setpda
// 支持enable_from_shared_this的核心
template <class Ux>
void _Set_ptr_rep_and_enable_shared(Ux* const ptr, Ref_count_base* const rep) noexcept {
this->_ptr = ptr;
this->_rep = rep;
if constexpr (std::conjunction_v<negation<is_array<Ty>>, negation<is_volatile<Ux>>, Can_enable_shared<Ux>>) { // Can_enable_shared在编译期判断是否继承于enable_from_shared_this
if (ptr && ptr->_Wptr.expired()) {
ptr->_Wptr = shared_ptr<std::remove_cv_t<Ux>>(*this, const_cast<std::remove_cv_t<Ux>*>(ptr)); // enable_from_shared_this的_Wptr此时才构造
}
}
}
void _Set_ptr_rep_and_enable_shared(nullptr_t, Ref_count_base* const rep) noexcept {
this->_ptr = nullptr;
this->_rep = rep;
}
template <class _Ty0, class... _Types>
friend shared_ptr<_Ty0> make_shared(_Types&&... _Args);
};
enable_from_shared_this
代码很简单,会发现enable_shared_from_this根本就没有构造函数来初始化 _Wptr
,再次证明了需要在shared_ptr中初始化,因此想要正确使用enable_from_shared_this,要先构造shared_ptr。
template <class Ty>
class enable_shared_from_this {
public:
using _Esft_type = enable_shared_from_this;
// Can_enable_shared类型萃取通过_Esft_type来判断是否继承于enable_from_shared_this
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); // 拷贝构造
}
weak_ptr<Ty> weak_from_this() noexcept {
return _Wptr;
}
weak_ptr<const Ty> weak_from_this() const noexcept {
return _Wptr;
}
protected:
constexpr enable_shared_from_this() noexcept : _Wptr() {}
enable_shared_from_this(const enable_shared_from_this&) noexcept : _Wptr() {}
enable_shared_from_this& operator=(const enable_shared_from_this&) noexcept {
return *this;
}
~enable_shared_from_this() = default;
private:
template <class _Yty>
friend class shared_ptr; // 需要声明友元
mutable weak_ptr<_Ty> _Wptr; // 不在这里初始化
};
make_shared
make_shared就是重新定义了一个控制块,该控制块 Ref_count_obj2
将包含数据块,这样控制块分配时,就会一块分配数据块和控制块。实现了一步走的功能。
也正因如此,make_shared有一个延迟释放资源的缺点,即便_Uses减为0了,_Weaks不为0,数据块资源依旧无法释放,直到_Weaks为0。
// STRUCT TEMPLATE _Wrap
template <class Ty>
struct _Wrap {
Ty _Value;
};
// 控制块:指针所指资源和控制块分配在同一内存
template <class Ty>
class Ref_count_obj2 : public Ref_count_base {
public:
template <class... Types>
explicit Ref_count_obj2(Types&&... Args){
std::_Construct_in_place(_Storage._Value, std::forward<Types>(Args)...);
// 其实就是placement new原地构造函数
}
~Ref_count_obj2() {} // 什么也不做,在_Destroy中析构
union {
_Wrap<Ty> _Storage;
};
private:
virtual void _Destroy() noexcept override { // destroy managed resource
std::_Destroy_in_place(_Storage._Value); // 其实就是placement delete
}
virtual void _Delete_this() noexcept override { // destroy self
delete this;
}
};
template <class Ty, class... Types>
shared_ptr<Ty> make_shared(Types&&... Args) { // make a shared_ptr
const auto Ref = new Ref_count_obj2<Ty>(std::forward<Types>(Args)...);
shared_ptr<Ty> result;
result._Set_ptr_rep_and_enable_shared(std::addressof(Ref->_Storage._Value), Ref);
return result;
}