从源码角度解读 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)

#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 的源码摘录下来,删掉了一些不太重要的逻辑以方便理解。代码如下:

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_ 属性。

 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_。 否则会匹配到一个空实现。

// 匹配到 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_