type
status
date
slug
summary
tags
category
icon
password
2024-09-23
今天研究 tcmalloc 的时候,看到其中使用到的 TList 侵入式链表的注释中有提到其中的 Elem 类需要使用 CRTP 来使用。
以前没咋用过,不过见过一个类似的用法(std::enable_shared_from_this),遂想研究一下其构成原理。
enable_shared_from_this 的作用
- 众所周知,使用同一个原始指针创建
shared_ptr
,是一种错误的行为,因为会导致资源被释放两次.
- 使用
this
指针创建shared_ptr
也是如此。但有时候我们确实需要这么做,例如我们需要在类内传递自己的shared_ptr
,又或是需要返回自己的shared_ptr
.
此时仅需要继承一下
enable_shared_from_this<T>
, 即可(一定要使用 public 继承,否则无法使用 shared_from_this() 方法)输出
可以看到,sp1 和 sp2 的引用计数都是正确的,因此不会发生重复释放的问题。而 sp3 和 sp4 则会出现重复释放的问题,导致程序异常退出。
注意:使用 enable_shared_from_this 必须建立在对象本身被 shared_ptr 管理的前提下,否则会出现 std::bad_weak_ptr 的异常问题(since C++17),这点看了实现原理即可一目了然。
enable_shared_from_this 实现原理
在看源码前,其实我们可以发现问题的本质就是对同一资源创建了不同的
shared_ptr
来管理,从而导致了引用计数不正确。那其实解决方法很简单,如果 this
指针本身就是一个 shared_ptr
,那么
我们后续返回或传递 this
指针(需要传递由shared_ptr管理的this)时就能得到正确的引用计数。事不宜迟,上源码~
我们使用的是
shared_from_this()
接口,它的实现非常简单,就是使用一个 weak_ptr 构建 shared_ptr,也就是说这个 **_M_weak_this** 存的就是我们的 this 指针。
但是奇怪的是,看了一圈,貌似找不到 **_M_weak_this** 是怎么用 this 来初始化的。。。看一下哪里写 **_M_weak_this** 了,发现
<1> _M_weak_assign
对 **_M_weak_this** 进行了初始化。那简单了,我们看一下这个
_M_weak_assign
到底是怎么被调用的。<2> _M_enable_shared_from_this_with
可以看到
_M_enable_shared_from_this_with
调用了 _M_weak_assign
,上面出现了模板函数重载,这里简单扩展一下,不想看可以直接 跳过。如果没看懂,可以取了解一下简单来说就是利用模板参数的替换失败来筛选模板。我们可以看
_M_enable_shared_from_this_with
的返回值是一串模板,我们逐步分析.
1. enable_if简单来说,它的作用就是:如果传入的第一个模板参数为 true,则其内部的 type 为第二个模板参数的类型;如果传入的第一个模板参数为 false,则其内部没有 type 类型。
在
_M_enable_shared_from_this_with
定义处,将 enable_if::type 作为返回值。(1) 如果 enable_if 的第一个模板参数为false则会生成一个空的实例,由于没有type类型,模板函数语法错误,无法生成实例,这个时候不会对**_M_weak_this** 进行赋值,最终在调用
shared_from_this()
时抛出异常(bad_weak_ptr since C++17)
(2) * 如果 第一个模板参数为 true 则生成正常的实例
那么我们现在观察一下,第一个参数是个啥?可以看到,enable_if 的第一个模板参数是
__has_esft_base<_YP>
。从名字我们不难看出,它的作用是判断类型 _YP
是否有一个 esft(enable_shared_from_this
)基类,当然如果有 __enable_shared_from_this
(有锁版本) 也可以。注:这里是否有一个 esft 基类是根据 调用
__enable_shared_from_this_base()
成功与否判断的。如果符合条件的话,_M_enable_shared_from_this_with 的有用的实例将被生成。
<3>__shared_ptr构造函数
_M_enable_shared_from_this_with 是被
__shared_ptr
构造函数调用的,这个 __shared_ptr
其实就是 std::shared_ptr
的基类了,这也就回答了一开始的 注意 ,如果不使用 shared_ptr
来管理,那么 _M_weak_this 压根不会
被初始化。。。总结
- 如果需要使用
this
指针构造std::shared_ptr<T>
,T 必须 public 继承std::enable_shared_from_this<T>
,且 T 的构造需要通过std::shared_ptr<T>
,否则毫无意义。也就是说如果你不打算用智能指针管理你的实例,你压根不需要考虑这些。
std::enable_shared_from_this
类成员 *_M_weak_this** 初始化的调用链 : shared_ptr构造函数 -> _M_enable_shared_from_this_with -> _M_weak_assign