enable_shared_from_this实现原理
2023-3-6
| 2025-3-6
Words 1590Read Time 4 min
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 的作用

  1. 众所周知,使用同一个原始指针创建 shared_ptr ,是一种错误的行为,因为会导致资源被释放两次.
  1. 使用 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 压根不会 被初始化。。。

总结

  1. 如果需要使用 this 指针构造 std::shared_ptr<T> ,T 必须 public 继承 std::enable_shared_from_this<T>,且 T 的构造需要通过 std::shared_ptr<T>,否则毫无意义。也就是说如果你不打算用智能指针管理你的实例,你压根不需要考虑这些。
  1. std::enable_shared_from_this 类成员 *_M_weak_this** 初始化的调用链 : shared_ptr构造函数 -> _M_enable_shared_from_this_with -> _M_weak_assign
 
Qt源码剖析之事件循环系统单例模式
Loading...