前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++ enable_shared_from_this 具体实现

C++ enable_shared_from_this 具体实现

作者头像
JoeyBlue
发布2022-11-21 15:45:10
1K0
发布2022-11-21 15:45:10
举报
文章被收录于专栏:代码手工艺人

C++ 中使用 std::shared_ptr 智能指针不当有可能会造成循环引用,因为 std::shared_ptr 内部是基于引用计数来实现的, 当引用计数为 0 时,就会释放内部持有的裸指针。但是当 a 持有 b, b 也持有 a 时,相当于 a 和 b 的引用计数都至少为 1,因此得不到释放,RAII 此时也无能为力。这时就需要使用 weak_ptr 来打破循环引用。

通过 weak_ptr 来避免循环引用

来看一个比较典型的 delegate/observer 的场景:

代码语言:javascript
复制
#include <iostream>
#include <memory>

class DataFetcher {
public:
	class Delegate {
	public:
		~Delegate() = default;
		virtual void OnDataReady(void* any_data) = 0;
	};

	DataFetcher(std::weak_ptr<Delegate> delegate) : delegate_(delegate) {}

	void FetchData() {
		// ... fetch data from somewhere asynchronously
		// and call back
		auto delegate = delegate_.lock();
		delegate->OnDataReady(nullptr);
	}
private:
	std::weak_ptr<Delegate> delegate_;
};

class DataManager : public DataFetcher::Delegate,
				  public std::enable_shared_from_this<DataManager> {
public:
	DataManager() {}

	void FetchData() {
		if (!data_fetcher_) {
			data_fetcher_ = std::make_shared<DataFetcher>(shared_from_this());
		}
		std::cout << "Will fetch data with data_fetcher_" << std::endl;
		data_fetcher_->FetchData();
	}
	void OnDataReady(void* any_data) override {
		std::cout << "Got Data!" << std::endl;
	}

private:
	std::shared_ptr<DataFetcher> data_fetcher_;
};


int main(int argc, char *argv[]) {
	auto manager = std::make_shared<DataManager>();
	manager->FetchData();
}

这里例子里, DataManager 通过 std::shared_ptr<DataFetcher> data_fetcher_ 强持有 DataFetcherDataFetch 通过 std::weak_ptr<Delegate> delegate_ 弱持有 DataManager。如果这里是 使用 std::shared_ptr<Delegate> delegate_ 强持有 DataManager 的话,那么 DataManagerDataFetch 将会造成循环引用,都得不到释放,造成内存泄漏。

可以看到,在构造 DataFetch 的时候, 我们使用了 shared_from_this() 作为参数: data_fetcher_ = std::make_shared<DataFetcher>(shared_from_this()); 它是 std::enable_shared_from_this<T> 类的一个方法。因为我们继承了 std::enable_shared_from_thi<T>,因此就可以拿到这个方法,它返回的是一个当前指针的 std::shared_ptr<T>.

那么它是怎么实现的呢? 查看文档, 有如下描述:

A common implementation for enable_shared_from_this is to hold a weak reference (such as std::weak_ptr) to this. The constructors of std::shared_ptr detect the presence of an unambiguous and accessible (ie. public inheritance is mandatory) (since C++17) enable_shared_from_this base and assign the newly created std::shared_ptr to the internally stored weak reference if not already owned by a live std::shared_ptr (since C++17).

意思就是说,内部会持有一个 weak_ptt<T> wp, shared_from_this() 内部检查是否实现了 enable_shared_from_this 基类,如果实现了,就会基于 wp 创建一个 shared_ptr 返回出来. 这样看起来挺巧妙。那么这个 weakptr 的指针是什么时候创建的呢?

enable_shared_from_this 源码实现

我们来扒一扒源码,先来看一下 enable_shared_from_this 模版类的实现,代码虽然不多,但是为了简单清晰,我把涉及不到的方法给移除掉了:

代码语言:javascript
复制
template<class _Tp>
class _LIBCPP_TEMPLATE_VIS enable_shared_from_this
{
    // private 的 weak_ptr 指针:
    mutable weak_ptr<_Tp> __weak_this_;
public:
    _LIBCPP_INLINE_VISIBILITY
    shared_ptr<_Tp> shared_from_this()
        {return shared_ptr<_Tp>(__weak_this_);}

#if _LIBCPP_STD_VER > 14
    _LIBCPP_INLINE_VISIBILITY
    weak_ptr<_Tp> weak_from_this() _NOEXCEPT
       { return __weak_this_; }
#endif // _LIBCPP_STD_VER > 14

    template <class _Up> friend class shared_ptr;
};

有这么几点需要注意的:

  1. 内部持有了 private 的 weak_ptr 指针 __weak_this_: mutable weak_ptr<_Tp> __weak_this_\
  2. shared_from_this() 直接返回的是 shared_ptr<_Tp>(__weak_this_), 并不是 __weak_this_.lock(), 原因是前者如果 __weak_this_ 如果为空,将会抛出异常,后者会返回一个存储 nullptrstd::shared_ptr 对象。
  3. C++ 14 之后,有 weak_from_this() 方法直接返回 __weak_this_
  4. class shared_ptr 设置为友元类,也就是说 shared_ptr 可以访问 enable_shared_from_this 的私有属性 __weak_this_

但是看不到什么时候给 __weak_this_ 初始化的。

shared_ptr 的部分源码

我们再拿出来 shared_ptr 源码来看下, shared_ptr 的源码较多,这里同样去掉一些不影响理解的逻辑。

代码语言:javascript
复制

template<class _Tp>
class shared_ptr
{
public:
    explicit shared_ptr(_Yp* __p) : __ptr_(__p) {
        unique_ptr<_Yp> __hold(__p);
        typedef typename __shared_ptr_default_allocator<_Yp>::type _AllocT;
        typedef __shared_ptr_pointer<_Yp*, __shared_ptr_default_delete<_Tp, _Yp>, _AllocT > _CntrlBlk;
        __cntrl_ = new _CntrlBlk(__p, __shared_ptr_default_delete<_Tp, _Yp>(), _AllocT());
        __hold.release();

        // 注意这里,在创建 shared_ptr 的时候,会调用 __enable_weak_this 这样一个方法:
        __enable_weak_this(__p, __p);
    }

private:
    // __enable_weak_this 主实现:
    template <class _Yp, class _OrigPtr>
        typename enable_if<is_convertible<_OrigPtr*,
                                          const enable_shared_from_this<_Yp>*
        >::value,
            void>::type
        __enable_weak_this(const enable_shared_from_this<_Yp>* __e,
                           _OrigPtr* __ptr) _NOEXCEPT
        {
            typedef typename remove_cv<_Yp>::type _RawYp;
            if (__e && __e->__weak_this_.expired())
            {
                __e->__weak_this_ = shared_ptr<_RawYp>(*this,
                    const_cast<_RawYp*>(static_cast<const _Yp*>(__ptr)));
            }
        }

     // __enable_weak_this 的兜底实现:
     void __enable_weak_this(...) _NOEXCEPT {}

};

我们可以注意到在 shared_ptr 的构造函数里,会调用 __enable_weak_this() 这样一个方法,有两个参数,把包装的裸指针 __p 传入进去

__enable_weak_this 函数主实现使用了模版源编程 Template meta programming,不熟悉的话,可能乍一看有点蒙,这个稍后再说,先看函数体:

代码语言:javascript
复制
__enable_weak_this(const enable_shared_from_this<_Yp>* __e,
                    _OrigPtr* __ptr) _NOEXCEPT
{
    typedef typename remove_cv<_Yp>::type _RawYp;
    // 检查 __e->__weak_this_ 是否为空,expired() 返回 true 表示内部对象为空
    // 如果为空的话,则通过this 指针和 ptr 构造出来一个 shared_ptr, 并存入 __weak_this_ 中。
    if (__e && __e->__weak_this_.expired())
    {
        __e->__weak_this_ = shared_ptr<_RawYp>(*this,
            const_cast<_RawYp*>(static_cast<const _Yp*>(__ptr)));
    }
}

到这里我们搞清楚了,enable_shared_from_this 里的 __weak_this_ 是谁创建的,以及在什么时机创建的:

Answer: 在创建 shared_ptr<T> 的时候(T 继承自 enable_shared_from_this<T>), 初始化了 enable_shared_from_this<T> 里的 __weak_this_ 指针。

Note: 如果仔细看的话,发现构造 shared_ptr 的时候有点奇怪,第一个参数是 shared_ptr<T> 类型,第二个是 __ptr 也就是当前 shared_ptr 对象管理的裸指针。 shared_ptr<_RawYp>(*this, const_cast<_RawYp*>(static_cast<const _Yp*>(__ptr))) 这个调用的是 std::shared_ptr 的别名构造函数(The aliasing constructor),意思是说,共享 r 参数的引用计数, 但是 .get() 返回的是 ptr 指针。 template< class Y > shared_ptr( const shared_ptr<Y>& r, element_type* ptr ) noexcept; // (8)

现在就剩下一个疑惑了,shared_ptr 怎么知道一个类型有没有继承自 enable_shared_from_this 呢? 这个就需要我们回过头来看 __enable_weak_this 的返回值类型,也就是下面这一坨:

代码语言:javascript
复制
typename enable_if<
            is_convertible<_OrigPtr*, const enable_shared_from_this<_Yp>*>::value,
            void>::type

对,这一坨最终会在编译期塌缩成一个类型,最终返回 void 或者空。当返回 void 时,__enable_weak_this 函数签名就是

代码语言:javascript
复制
void __enable_weak_this(const enable_shared_from_this<_Yp>* __e,
                    _OrigPtr* __ptr)

当塌缩成空时,__enable_weak_this 函数签名就是

代码语言:javascript
复制
__enable_weak_this(const enable_shared_from_this<_Yp>* __e,
                    _OrigPtr* __ptr)

显然这是一个不合法的签名,因此编译期发现整个不合法,就不生成这个函数了。 这个就是模板元编程的特点,编译器生成模版函数和我们手写函数的逻辑完全不同,我们手写的函数不合法,编译器就会报错,但是如果编译器生成出来的发现不合法,编译器就会不生成这个函数。 这个就是所谓的 SFINAE (Substitue Failure Is Not An Error) ,翻译过来就是:(模版)替换失败不是一个错误。

现在有两个问题:

  1. 什么条件下返回void 以及 空 呢?
  2. 如果不生成 __enable_weak_this 函数, 那构造里调用的函数,是调的哪个呢?

对于第二个问题,比较简单,上面我们发现有个兜底的 __enable_weak_this 函数, 调用的就是这个了,内部实现是空的,也就是什么也不做。

代码语言:javascript
复制
// __enable_weak_this 的兜底实现:
void __enable_weak_this(...) _NOEXCEPT {}

对于第一个问题,就是 enable_if 起的作用: enable_if<bool 值, 类型T>::type 的意思是说,如果bool值为trueenable_if 返回的就是第二个模版参数 类型T, 如果为false,返回空(不是void,而是什么也没有) 那么看下:

代码语言:javascript
复制
enable_if<is_convertible<_OrigPtr*, const enable_shared_from_this<_Yp>*>::value, void>::type

意思就是说,如果 is_convertible<_OrigPtr*, const enable_shared_from_this<_Yp>*>::value 返回 true 的话,也就是说我们的裸指针可以转换为 enable_shared_from_this<_Yp>*>::value, 其实也就是说,我们的裸指针类型是继承自 enable_shared_from_this<_Yp> 的。

所以这句话的意思就是说,如果传入的裸指针类型是继承自 enable_shared_from_this 的,那么 返回 void 类型,否则返回空,让 __enable_weak_this 函数替换失败,导致内部无法创建 __weak_this_ 指针,也就没办法通过 shared_from_this() 函数拿到当前this指针对应的 shared_ptr.

以上。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-11-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 通过 weak_ptr 来避免循环引用
  • enable_shared_from_this 源码实现
  • shared_ptr 的部分源码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档