原文出处:C++那些事》之enable_shared_from_this

std::enable_shared_from_this它被用作需要创建与现有对象共享所有权的 std::shared_ptr 实例的类的基类。

使用 enable_shared_from_this 的类必须继承自 enable_shared_from_this

当一个类 A 继承了 std::enable_shared_from_this<A>,并在某个地方创建了一个 std::shared_ptr<A> 的智能指针时,通过调用 shared_from_this 函数可以得到一个指向该对象的std::shared_ptr,且该 std::shared_ptr 与原始的 std::shared_ptr 共享对象的所有权。

例如:

class Foo  
    : public std::enable_shared_from_this<Foo>   
{  
}

简单实现如下

std::enable_shared_from_this 是一个模板类,定义如下:

template<class T>  
class enable_shared_from_this  
{  
protected:  
    constexpr enable_shared_from_this() noexcept;  
    enable_shared_from_this(const enable_shared_from_this&) noexcept;  
    enable_shared_from_this& operator=(const enable_shared_from_this&) noexcept;  
    ~enable_shared_from_this();  
public:  
    shared_ptr<T> shared_from_this();  
    shared_ptr<const T> shared_from_this() const;  
};

enable_shared_from_this 模板类的实现依赖于两个关键类,即 std::shared_ptrstd::weak_ptr。它的核心思想是通过将 std::shared_ptr 对象作为类成员变量保存,并在需要共享该对象所有权的地方返回一个指向该对象的 std::shared_ptr

enable_shared_from_this 类的构造函数中,将 weak_this 成员变量初始化为一个空的std::weak_ptr。该成员变量用于保存指向该类的 std::shared_ptr 对象,该对象是通过调用 shared_from_this 函数返回的。

标准库的std::enable_shared_from_this 的实现大致如下:

template<typename _Tp>
    class enable_shared_from_this
    {
    protected:
      constexpr enable_shared_from_this() noexcept { }

      enable_shared_from_this(const enable_shared_from_this&) noexcept { }

      enable_shared_from_this&
      operator=(const enable_shared_from_this&) noexcept
      { return *this; }

      ~enable_shared_from_this() { }

    public:
      shared_ptr<_Tp>
      shared_from_this()
      { return shared_ptr<_Tp>(this->_M_weak_this); }

      shared_ptr<const _Tp>
      shared_from_this() const
      { return shared_ptr<const _Tp>(this->_M_weak_this); }

#if __cplusplus > 201402L || !defined(__STRICT_ANSI__) // c++1z or gnu++11
#define __cpp_lib_enable_shared_from_this 201603
      weak_ptr<_Tp>
      weak_from_this() noexcept
      { return this->_M_weak_this; }

      weak_ptr<const _Tp>
      weak_from_this() const noexcept
      { return this->_M_weak_this; }
#endif

    private:
      template<typename _Tp1>
 void
 _M_weak_assign(_Tp1* __p, const __shared_count<>& __n) const noexcept
 { _M_weak_this._M_assign(__p, __n); }

      // Found by ADL when this is an associated class.
      friend const enable_shared_from_this*
      __enable_shared_from_this_base(const __shared_count<>&,
         const enable_shared_from_this* __p)
      { return __p; }

      template<typename, _Lock_policy>
 friend class __shared_ptr;

      mutable weak_ptr<_Tp>  _M_weak_this;
    };

在构造函数中,weak_this_ 被初始化为一个空的 std::weak_ptr。在使用 shared_from_this 函数时,它将返回一个指向 this 对象的 std::shared_ptr,如果此时没有任何 std::shared_ptr 对象管理该对象,将会抛出 std::bad_weak_ptr 异常。

需要注意的是,shared_from_this 函数必须在至少存在一个 std::shared_ptr 对象管理该对象时才能调用,否则将会出现未定义的行为。同时,shared_from_this 函数也不能在对象的构造函数中被调用,因为此时还没有任何 std::shared_ptr 对象管理该对象。

M_weak_this_ 是一个 std::weak_ptr 对象,它保存的是指向当前对象的一个弱引用(weak reference),而不是强引用(shared reference)。

std::enable_shared_from_this 的实现中,wM_eak_this_ 被初始化为一个空的 std::weak_ptr,因为此时还没有任何 std::shared_ptr 对象管理当前对象,所以也就无法通过任何 std::shared_ptr 对象来创建一个指向当前对象的 std::shared_ptr

当我们在某个地方创建了一个 std::shared_ptr 对象来管理当前对象时,我们需要在这个 std::shared_ptr 对象创建之后,调用 std::enable_shared_from_this 中提供的 shared_from_this 函数来获得一个指向当前对象的 std::shared_ptr。在这个过程中,shared_from_this 函数将使用 _M_weak_this 中保存的弱引用来创建一个指向当前对象的 std::shared_ptr

需要注意的是,如果在任何一个时刻,没有任何 std::shared_ptr 对象管理当前对象,那么 _M_weak_this 中保存的弱引用就会被销毁,此时再尝试通过 shared_from_this 函数来创建一个指向当前对象的 std::shared_ptr,将会抛出 std::bad_weak_ptr 异常。因此,使用 std::enable_shared_from_this 时需要特别注意对象的生命周期和 shared_ptr 的管理方式。

enable_shared_from_this可以使用是在shared_ptr构造过程中,有相应的设置过程。

shared_ptr调用构造__shared_ptr__shared_ptr构造会调用执行_M_enable_shared_from_this_with_M_enable_shared_from_this_with调用执行__base->_M_weak_assign的过程,此时会构造_M_weak_this


原文出处:一场由私有继承enable_shared_from_this引发的血案

起因

众所周知, std::enable_shared_from_this 是以奇异递归模板( CRTP)实现的一个模板类。在日常开发中,我们可以继承 std::enable_shared_from_this 进而拿到 this 指针的shared_ptr 版本 —— shared_from_this

但在使用上,好像没有明确规定公有继承( public )还是私有继承( private ),甚至因为 cppreference 上用 struct 示例的缘故,很多 CXDN 的博客换了个写法,就变成以class 私有继承来示例。

那么使用私有继承会存在什么问题呢?

class Bar : std::enable_shared_from_this<Bar>
{
public:
 Bar(std::string data) : data_(std::move(data)) {}

 template<typename CallBack>
 void start(CallBack&& cb) {
  thd_ = std::thread([this, self = shared_from_this(), notify_cb = std::move(cb)] {
    notify_cb(data_);
   });
 }

private:
 std::thread thd_;
 std::string data_;
};

以上代码是一个简化版的应用场景。我有一个私有继承 std::enable_shared_from_this 的类 Bar ,在 start() 方法里面传递一个用于通知的回调函数,这个回调函数会在类 Bar 的子线程里面异步执行通知命令。调用方式大概如下所示:

std::vector<std::shared_ptr<Bar>> bars;  
{  
    auto bar_ptr = std::make_shared<Bar>("hoho");  
    bar_ptr->start([](const std::string& info) {  
        std::cout << info << std::endl;  
    });  
    bars.push_back(bar_ptr);  
}

代码看起来不像有什么问题,但一运行就 crash 。通过栈回溯找到原因 —— 触发 bad_weak_ptr 异常。深入定位,问题出在:

thd_ = std::thread([this, self = shared_from_this(), notify_cb = std::move(cb)] {

这一行,经排查去掉捕获 self = shared_from_this() 就好了,难不成 shared_from_this() 出现了问题?

深入源码

std::enable_shared_from_this 的源码还是非常简单的,我们一眼就看到调用 shared_from_this() 关联的变量 _Wptr ,但是 _Wptrstd::enable_shared_from_this 的默认构造里面初始化为空,我们只能继续深入在哪里对 _Wptr 进行赋值。

由于我们是通过 std::make_shared 构造的 shared_ptr ,通过源码我们定位到函数 _Set_ptr_rep_and_enable_shared

显而易见,这会调用第一个 _Set_ptr_rep_and_enable_shared 函数,同时我们看到了熟悉的 _Wptr

我们先从简单的逻辑入手,很明显 _Px && _Px->_Wptr.expired() 的结果是 true 。那么决定 _Wptr 赋值的关键条件就是 conjunction_v<negation<is_array<_Ty>>, negation<is_volatile<_Ux>>, _Can_enable_shared<_Ux>>

conjunction_v<T1, T2, T3...>true 成立的条件是同时满足 T1, T2, T3... 均为 truenegation<_T> 的作用是条件 _T 不成立时为 true

显然 negation<is_array<_Ty>>negation<is_volatile<_Ux>> 均为 true ,那么问题再次简化为 _Can_enable_shared<_Ux> 是否成立。

翻开 _Can_enable_shared 的源码, _Can_enable_shared<_Yty>true_type 的决定因素在于能否将类型 _Yty 转换为 _Yty::_Esft_type 类型( is_convertible )。

这个 _Esft_type是不是看起来眼熟?没错,它就在 std::enable_shared_from_this 的源码最前面:

class enable_shared_from_this { // provide member functions that create shared_ptr to this  
public:  
    using _Esft_type = enable_shared_from_this;  
...

当我们私有继承 std::enable_shared_from_this 时,是无法对外拿到 _Yty::_Esft_type 的类型使用权限的,进而 SFINAE 匹配到 _Can_enable_sharedfalse_type,以至于不能满足条件 conjunction_v<negation<is_array<_Ty>>, negation<is_volatile<_Ux>>, _Can_enable_shared<_Ux>> ,从而使 _Wptr 为空,在调用 shared_from_this() 时引发 bad_weak_ptr 异常。

总结

以上是以 Windows - vs2022(msvc14.3) 为示例,实际在 Linux - gcc/clang 编译器下均存在此问题。

在日常开发中,必须以公有继承( public )的方式来继承 std::enable_shared_from_this,不然血案在向你招手。