C++ std::enable_shared_from_this 具体实现
C++ 中使用 std::shared_ptr
智能指针不当有可能会造成循环引用,因为 std::shared_ptr
内部是基于引用计数来实现的, 当引用计数为 0 时,就会释放内部持有的裸指针。但是当 a 持有 b, b 也持有 a 时,相当于 a 和 b 的引用计数都至少为 1,因此得不到释放,RAII 此时也无能为力。这时就需要使用 weak_ptr 来打破循环引用。
通过 weak_ptr 来避免循环引用
来看一个比较典型的 delegate/observer 的场景:
1 |
|
这里例子里, DataManager
通过 std::shared_ptr<DataFetcher> data_fetcher_
强持有 DataFetcher
,DataFetch
通过 std::weak_ptr<Delegate> delegate_
弱持有 DataManager
。如果这里是 使用 std::shared_ptr<Delegate> delegate_
强持有 DataManager
的话,那么 DataManager
和 DataFetch
将会造成循环引用,都得不到释放,造成内存泄漏。
可以看到,在构造 DataFetch
的时候, 我们使用了 shared_from_this()
作为参数:data_fetcher_ = std::make_shared<DataFetcher>(shared_from_this());
它是 std::enable_shared_from_this<T>
类的一个方法。因为我们继承了 std::enable_shared_from_thi<T>
,因此就可以拿到这个方法,它返回的是一个当前指针的 std::shared_ptr<T>
.
那么它是怎么实现的呢? 查看文档, 有如下描述:
A common implementation for enable_shared_from_this is to hold a weak reference (such as std::weak_ptr) to this. The constructors of std::shared_ptr detect the presence of an unambiguous and accessible (ie. public inheritance is mandatory) (since C++17) enable_shared_from_this base and assign the newly created std::shared_ptr to the internally stored weak reference if not already owned by a live std::shared_ptr (since C++17).
意思就是说,内部会持有一个 weak_ptt<T> wp
, shared_from_this()
内部检查是否实现了 enable_shared_from_this
基类,如果实现了,就会基于 wp 创建一个 shared_ptr
返回出来。这样看起来挺巧妙。那么这个 weakptr 的指针是什么时候创建的呢?
enable_shared_from_this 源码实现
我们来扒一扒源码,先来看一下 enable_shared_from_this
模版类的实现,代码虽然不多,但是为了简单清晰,我把涉及不到的方法给移除掉了:
1 | template<class _Tp> |
有这么几点需要注意的:
- 内部持有了 private 的 weak_ptr 指针
__weak_this_
:mutable weak_ptr<_Tp> __weak_this_
shared_from_this()
直接返回的是shared_ptr<_Tp>(__weak_this_)
, 并不是__weak_this_.lock()
, 原因是前者如果__weak_this_
如果为空,将会抛出异常,后者会返回一个存储nullptr
的std::shared_ptr
对象。- C++ 14 之后,有
weak_from_this()
方法直接返回__weak_this_
- 把
class shared_ptr
设置为友元类,也就是说shared_ptr
可以访问enable_shared_from_this
的私有属性__weak_this_
但是看不到什么时候给 __weak_this_
初始化的。
shared_ptr 的部分源码
我们再拿出来 shared_ptr
源码来看下,shared_ptr
的源码较多,这里同样去掉一些不影响理解的逻辑。
1 |
|
我们可以注意到在 shared_ptr
的构造函数里,会调用 __enable_weak_this()
这样一个方法,有两个参数,把包装的裸指针 __p 传入进去
__enable_weak_this
函数主实现使用了模版源编程 Template meta programming
,不熟悉的话,可能乍一看有点蒙,这个稍后再说,先看函数体:
1 | __enable_weak_this(const enable_shared_from_this<_Yp>* __e, |
到这里我们搞清楚了,enable_shared_from_this
里的 __weak_this_
是谁创建的,以及在什么时机创建的:
Answer: 在创建 shared_ptr<T>
的时候 (T 继承自 enable_shared_from_this<T>
), 初始化了 enable_shared_from_this<T>
里的 __weak_this_
指针。
Note:
如果仔细看的话,发现构造shared_ptr
的时候有点奇怪,第一个参数是shared_ptr<T>
类型,第二个是__ptr
也就是当前shared_ptr
对象管理的裸指针。
1 shared_ptr<_RawYp>(*this, const_cast<_RawYp*>(static_cast<const _Yp*>(__ptr)))这个调用的是
std::shared_ptr
的别名构造函数(The aliasing constructor),意思是说,共享 r 参数的引用计数, 但是.get()
返回的是 ptr 指针。
1
2 template< class Y >
shared_ptr( const shared_ptr<Y>& r, element_type* ptr ) noexcept; // (8)
现在就剩下一个疑惑了,shared_ptr
怎么知道一个类型有没有继承自 enable_shared_from_this
呢?
这个就需要我们回过头来看 __enable_weak_this
的返回值类型,也就是下面这一坨:
1 | typename enable_if< |
对,这一坨最终会在编译期塌缩成一个类型,最终返回 void
或者空。当返回 void
时,__enable_weak_this
函数签名就是
1 | void __enable_weak_this(const enable_shared_from_this<_Yp>* __e, |
当塌缩成空时,__enable_weak_this
函数签名就是
1 | __enable_weak_this(const enable_shared_from_this<_Yp>* __e, |
显然这是一个不合法的签名,因此编译期发现整个不合法,就不生成这个函数了。
这个就是模板元编程的特点,编译器生成模版函数和我们手写函数的逻辑完全不同,我们手写的函数不合法,编译器就会报错,但是如果编译器生成出来的发现不合法,编译器就会不生成这个函数。
这个就是所谓的 SFINAE
(Substitue Failure Is Not An Error) ,翻译过来就是:(模版)替换失败不是一个错误。
现在有两个问题:
- 什么条件下返回
void
以及 空 呢? - 如果不生成
__enable_weak_this
函数, 那构造里调用的函数,是调的哪个呢?
对于第二个问题,比较简单,上面我们发现有个兜底的 __enable_weak_this
重载函数,调用的就是这个了,内部实现是空的,也就是什么也不做。
1 | // __enable_weak_this 的兜底实现: |
对于第一个问题,就是 enable_if
起的作用:enable_if<bool 值, 类型T>::type
的意思是说,如果 bool
值为 true
,enable_if
返回的就是第二个模版参数 类型T
, 如果为 false
,返回空(不是 void
,而是什么也没有)
那么看下:
1 | enable_if<is_convertible<_OrigPtr*, const enable_shared_from_this<_Yp>*>::value, void>::type |
意思就是说,如果 is_convertible<_OrigPtr*, const enable_shared_from_this<_Yp>*>::value
返回 true
的话,也就是说我们的裸指针可以转换为 enable_shared_from_this<_Yp>*>::value
, 其实也就是说,我们的裸指针类型是继承自 enable_shared_from_this<_Yp>
的。
所以这句话的意思就是说,如果传入的裸指针类型是继承自 enable_shared_from_this
的,那么 返回 void
类型,否则返回空,让 __enable_weak_this
函数替换失败,导致内部无法创建 __weak_this_
指针,也就没办法通过 shared_from_this()
函数拿到当前 this
指针对应的 shared_ptr
.
避免在构造函数里调用 shared_from_this ()
来看下面这个场景,在构造里注册 Observer,然后为了避免循环引用,这里我们传入一个 weak_ptr,看起来非常合理,你能看出来有什么问题吗?
1 |
|
结果就是马上 crash 掉,如果对内部原理不清楚的话,很难一下子找到根本原因.
1 | libc++abi: terminating with uncaught exception of type std::__1::bad_weak_ptr: bad_weak_ptr |
根据上面我们看 shared_from_this () 的源码实现会发现它是通过 _weak_this 来构造出来的,不管是 make_shared 内部会先调用 new DataManager 创建指针,然后再创建 _weak_this_,因此在 DataManager 构造函数被调用时,__weak_this 还没有创建出来,因此会报 bad_weak_ptr 的错误。
1 | shared_ptr<_Tp> shared_from_this() |
以上。