从源码角度解读 enable_shared_from_this

我们在使用 C++ 的时候,有时会需要在类的内部获取自身的 shared_ptr,这就会用到 std::enable_shared_from_this。在实际使用过程中,std::enable_shared_from_this 有三个陷阱需要注意:

  1. 不能在构造函数中使用 shared_from_this(), 否则会抛出 std::bad_weak_ptr 异常。对应下面情况 1。
  2. 创建的对象必须由 shared_ptr 管理,shared_from_this() 才能生效,否则也会报 std::bad_weak_ptr 异常。对应下面情况 2。
  3. 对应类必须 public 继承 std::enable_shared_from_this, 不能是 protected 或 private 继承,否则也会报 std::bad_weak_ptr 异常。对应下面情况 3。

以上 case 均可以通过 wandbox 复现。

那么为什么会有这些限制呢?本文将从 std::enable_shared_from_this 的源码角度解读其原因。(本文基于 clang libc++ 的源码实现进行解读, 代码地址:shared_ptr.h#L1433)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <memory>

// 情况 1:在构造函数中使用 shared_from_this
class Case1 : public std::enable_shared_from_this<Case1>{
public:
Case1(){
// 抛异常:terminating due to uncaught exception of type std::__1::bad_weak_ptr: bad_weak_ptr
auto case1 = shared_from_this();
}
};

// 情况 2:不使用 shared_ptr 管理对象
class Case2 : public std::enable_shared_from_this<Case2>{
public:
std::shared_ptr<Case2> get_shared_ptr() {
// 抛异常:terminating due to uncaught exception of type std::__1::bad_weak_ptr: bad_weak_ptr
return shared_from_this();
}
};

// 情况 3:未 public 继承 std::enable_shared_from_this
class Case3 : std::enable_shared_from_this<Case3>{
public:
std::shared_ptr<Case3> get_shared_ptr() {
// 抛异常:terminating due to uncaught exception of type std::__1::bad_weak_ptr: bad_weak_ptr
return shared_from_this();
}
};

int main(){
// 情况 1
auto c1 = std::make_shared<Case1>();

// 情况 2
Case2* c2 = new Case2();
c2->get_shared_ptr();

// 情况 3
auto c3 = std::make_shared<Case3>();
c3->get_shared_ptr();

return 0;
}

我把 enable_shared_from_this 的源码摘录下来,删掉了一些不太重要的逻辑以方便理解。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
template <class _Tp>
class enable_shared_from_this {
mutable weak_ptr<_Tp> __weak_this_;

public:
shared_ptr<_Tp> shared_from_this() {
return shared_ptr<_Tp>(__weak_this_);
}

template <class _Up>
friend class shared_ptr;
};

从代码可以看出 enable_shared_from_this 核心的就是一个 weak_ptr 属性 __weak_this_ 。而 shared_from_this 其实就是把 weak_ptr 转换成 shared_ptr。

那么问题来了,__weak_this_ 是在什么时候设置呢?答案是:在创建 shared_ptr 对象的时候。

以下是 shared_ptr 中创建对象的逻辑,其中在 __enable_weak_this 中设置了 enable_shared_from_this 的 __weak_this_ 属性。

1
2
3
4
5
6
7
8
9
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;
// 设置__weak_this_
__r.__enable_weak_this(__r.__ptr_, __r.__ptr_);
return __r;
}

__enable_weak_this 的实现中,因为 enable_shared_from_this 类里面将 shared_ptr<T> 设置为了 friend class。因此 shared_ptr 可以直接访问并设置 enable_shared_from_this 的 __weak_this_ 属性。

同时,__enable_weak_this 使用 SFINAE 实现了一个模板匹配,即:只有当满足 __enable_if_t<is_convertible<_OrigPtr*, const enable_shared_from_this<_Yp>*>::value, int> = 0 时(即对应类可以转换成 enable_shared_from_this,也就是类 public 继承了 enable_shared_from_this), 才会设置 __weak_this_。 否则会匹配到一个空实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 匹配到 enable_shared_from_this
template <class _Yp,
class _OrigPtr,
__enable_if_t<is_convertible<_OrigPtr*, const enable_shared_from_this<_Yp>*>::value, int> = 0>
void __enable_weak_this(const enable_shared_from_this<_Yp>* __e, _OrigPtr* __ptr) _NOEXCEPT {
typedef __remove_cv_t<_Yp> _RawYp;
if (__e && __e->__weak_this_.expired()) {
__e->__weak_this_ = shared_ptr<_RawYp>(*this, const_cast<_RawYp*>(static_cast<const _Yp*>(__ptr)));
}
}

// 空实现
void __enable_weak_this(...) _NOEXCEPT {}

解读完源码之后,一切情况非常明了。我们再回头看下文章刚开始提到的三个陷阱:

  • 情况 1:不能在构造函数中使用 shared_from_this()。这是因为整个过程是:先创建好了原始对象,再去设置 __weak_this_ 属性,最终才能得到一个 shared_ptr 对象。所以在执行原始对象的构造函数时,__weak_this_ 属性尚未设置,当然不能用 shared_from_this。
  • 情况 2:创建的对象必须由 shared_ptr 管理,shared_from_this() 才能生效。这是因为,只有在 shared_ptr 里面才会设置 __weak_this_
  • 情况 3:对应类必须 public 继承 std::enable_shared_from_this。因为只有 public 继承,才能正确匹配到对应的 __enable_weak_this,从而设置 __weak_this_