本文除 boost 源码以外,大部分源码都是伪代码,为了能够更清晰的理解问题,去掉了 模板相关的内容。文中很多地方直接使用了 sp 一词表示 shared_ptr
使用智能指针难以避免的场景之一就是需要把 this 当成一个 shared_ptr 传递到其他函数 中去。比如你的类实现了某个接口,而你需要把自己注册为该接口的实现。你不能简单粗暴 的用 this 指针构造一个 shared_ptr,因为那样做会导致两个独立的 shared_ptr 指向同 一份资源,这对于基于引用计数的 shared_ptr 来说是致命的。比如下面的代码。
1 | class Widget { |
正确的打开方式是继承来自 std::enable_shared_from_this,然后调用它的 shared_from_this 成员函数
1 | class Widget : public std::enable_shared_from_this<Widget> { |
把自己作为基类的模板参数看起来非常诡异,它有一个更诡异的名字——奇异递归模板模式。 关于它的使用这里不过多的介绍,有兴趣的同学可以参考《More Effective C++》,《C++ Templates》等书籍。这里主要探讨的是它是如何实现返回自身的智能指针的。
避免多个独立的 shared_ptr 指向同一份资源
在之前的错误代码中,有两个独立的 shared_ptr 指向同一块空间
1 | shared_ptr<Widget>(this); // 成员函数中 |
很显然 main
函数中这这个 shared_ptr 必不可少,所以我们只能去掉
shared_ptr<Widget>(this)
而让它共享 main 函数中创建的这个 shared_ptr。
如何共享 make_shared 的返回的资源
实际上,抛开一切来讲,我们想要的不是基于 this 去创建一个 shared_ptr
make_shared 会调用 shared_ptr 的构造函数,在 shared_ptr 的构造函数中,this 的类
型就是 shared_ptr
1 | // 下面所有的代码省去模板相关的代码,以便阅读。 |
建立 w 和 sp 之间的映射关系
建立 w 和 sp 的关系最直接的方式是使用一个 map 来集中管理,另一种方式就是采用分布 式的管理方式,也就是让 w 直接存储这个 sp,而不用借助额外的 map。
1 | class shared_ptr<Widget> { |
抽象公共基类
上面的设计的最大的问题是,你需要在自己的内部定义一个 shared_ptr 类型的成员变量以 及提供一个 GetPtr 和 SetSharedPtr 成员方法,这些代码和 Widget 类的业务逻辑可能没 有太大的关系,而且不适合重用。为了抽象并重用这些代码,我们可以抽象出一个公共的基 类,这也就是 std::enable_shared_from_this 实际上做的事情
1 | class enable_shared_from_this { |
上面这段代码最大的漏洞在于,shared_ptr 是一个模板,它并不知道 Widget 类是否继承
自 enable_shared_from_this,所以 w->SetSharedPtr(this)
这一句的调用不完全正确
。boost 中解决这个问题使用了一个非常巧妙的方法——重载,通过重载我们可以让编译器自
动选择是否调用 SetSharedPtr。
1 | class shared_ptr<Widget> { |
这段代码的精妙,让人叹为观止。
最后一个问题
上面这些代码的逻辑上是正确的,但是实际上还有一个巨大的BUG,那就是 Widget 的内部
存在一个指向它自己的 shared_ptr
普通的 shared_ptr 循环依赖的问题的处理通常使用的是 weak_ptr,这一次也不例外。我 们需要存储一个 weak_ptr 作为成员变量,而不是 shared_ptr,然后在需要的时候通过 weak_ptr 构建出 shared_ptr,所以正确的打开方式是:
1 | class enable_shared_from_this { |
这是一个接近正确答案的写法,也是一个比较容易理解的方法,但和实际上的写法还一些细
微的差别,比如实际上 SetSharedPtr 中并没有把 sp 直接赋值给 wp,而是使用了
shared_ptr 的别名构造函数
,为什么这么写,个人能力有限,还没弄清楚,弄懂了再回
来补充。
boost 的源码片段
上面的所有代码都是个人按照自己的理解写出来的,为了方便理解,这里给出 boost 的相 关源码片段,以供对比参考(需要注意的是 pn 和引用技术有关,但是和这里讨论的 enable_shared_from_this 没有太大的关联,你可以忽略它):
1 | template<class T> class shared_ptr |
1 | template< class T, class Y > inline void sp_pointer_construct( boost::shared_ptr< T > * ppx, Y * p, boost::detail::shared_count & pn ) |
1 | template< class X, class Y, class T > inline void sp_enable_shared_from_this( boost::shared_ptr<X> const * ppx, Y const * py, boost::enable_shared_from_this< T > const * pe ) |
1 | template<class T> class enable_shared_from_this |