使用举例

有时候我们需要在一个被 shared_ptr 管理的对象的内部获取自己的 shared_ptr, 比如下面这个简单的例子:

  • 通过 this 指针来构造一个 shared_ptr:
 struct Bad
{
void fun()
{
shared_ptr<Bad> sp{this};
cout<<sp->count()<<endl;
}
};
shared_ptr<Bad> sp{make_shared<Bad>()};
sp->fun(); //输出为1

但是注意, 在 func 函数构造智能指针时, 我们无法确定这个对象是不是被 shared_ptr 管理着, 因此这样构造的 shared_ptr 并不是与其他 shared_ptr 共享一个计数器, 那么, 在析构时就会导致对象被重复释放, 从而引发错误.

现在明确一下我们的需求: 在一个对象内部构造该对象的 shared_ptr 时, 即使该对象已经被 shared_ptr 管理着, 也不会造成对象被两个独立的智能指针管理.

这就要求我们在对象内构造对象的智能指针时, 必须能识别有对象是否已经由其他智能指针管理, 智能指针的数量, 并且我们创建智能指针后也能让之前的智能指针感知到.

正确做法是继承 enable_shared_from_this 类, 调用 shared_from_this() 函数生成 shared_ptr, 使用如下:

 struct Good : public std::enable_shared_from_this<Good>
{
  void fun()
  {
    shared_ptr<Good> sp{shared_from_this()};
    cout<<sp->count()<<endl;
  }
};
shared_ptr<Good> sp{make_shared<Good>()};//--------------*1*
sp->fun(); //输出为2

在类内部通过 enable_shared_from_this 定义的 shared_from_this() 函数构造一个 shared_ptr<Good> 对象, 能和其他 shared_ptr 共享 Good 对象.

enable_shared_from_this的实现分析(基于gcc-7.2.0的源码)

gcc是通过 weak_ptr 来实现的. 先用要管理对象(obj)的指针和已有的管理obj的 shared_ptr(sp1,...,spn) 的个数(spi->use_count())来初始化一个 weak_ptr<Obj>(&obj , spi->use_count()), 然后用这个 weak_ptr 构造一个 shared_ptr.

 // enable_shared_from_this的实现
// 基于(/usr/include/c++/7.3.0/bits/shared_ptr.h)
// 此代码是对gcc实现的简化版本, 仅作为描述原理用.
template<typename T>
class enable_shared_from_this
{
public:
shared_ptr<T> shared_from_this()
{
return shared_ptr<T>(this->weak_this);
}
shared_ptr<const T> shared_from_this() const
{
return shared_ptr<const T>(this->weak_this);
}
private:
template<typename>
friend class shared_ptr; template<typename T1>
void _M_weak_assign(T1* p, const shared_count<>& n)
{
  weak_this._M_assign(p, n);
} mutable weak_ptr<T> weak_this;
};

enable_shared_from_this<T> 类中定义了一个 weak_ptr<T>, 起到了上文提到的从obj指针生成 shared_ptr<T> 对象的作用. 按照先前的原理, 我们可能认为是在obj初始化的时候, 同时对 weak_this 进行初始化, 但是在这段代码里显然没有对 weak_this 进行任何初始化工作(原始代码里也没有, gcc为什么不这样实现呢? 这是因为当对象没有由智能指针管理时, 这些操作是没有必要的. 所以应该把这个任务交给 shared_ptr).

gcc在 shared_ptr<T> 的构造函数中对 weak_ptr<T> 进行处理. 从 Good 类来看, 就是在 *1* 处对 Good 对象中的 weak_ptr<Good> weak_this 进行处理, 使其指向一个有效的 Good 对象, 并修改 use_count. 上面 Good 类对 enable_shared_from_this 的使用是少数几种有效的方法, 必须保证, 如果对一个对象调用 shared_from_this(), 该对象必须是由 shared_ptr<T> 持有的. 从上一段的原理中可以理解这样做的原因: 第一个持有 Good 对象 g_objshared_ptr<T> sp1 会对 g_objweak_this 进行处理, 使其有效. 如果没有这一步, 在调用 shared_from_this() 时, weak_this 是一个无效值, 即 weak_this.expire() == true, 就会抛出异常.

那么在 shared_ptr 的构造函数中是如何处理 weak_ptr 的呢?

shared_ptr 中定义了这样一个函数(来自/usr/include/c++/7.3.0/bits/shared_ptr_base.h中类__shared_ptr):

 template<typename _Yp, typename _Yp2 = typename remove_cv<_Yp>::type>
typename enable_if<__has_esft_base<_Yp2>::value>::type
_M_enable_shared_from_this_with(_Yp* __p) noexcept
{
if(auto __base = __enable_shared_from_this_base(_M_refcount, __p))
__base->_M_weak_assign(const_cast<_Yp2*>(__p), _M_refcount);
} template<typename _Yp, typename _Yp2 = typename remove_cv<_Yp>::type>
typename enable_if<!__has_esft_base<_Yp2>::value>::type
_M_enable_shared_from_this_with(_Yp*) noexcept { }

其中 _Ypshared_ptr 管理的对象的类型. 这两个模板函数表示:

_Yp enable_shared_from_this 的子类时, 就会生成第一个函数, 其功能是通过 _Yp 对象的指针来调用其 _M_weak_assign 函数以修改 _Yp 对象的 weak_this 成员, 而实际上 _M_weak_assign 调用的是 _M_assign 函数.

否则生成第二个函数体为空的函数.

 // from shared_ptr_base.h class __weak_ptr, derived by weak_ptr

 void _M_assign(_Tp* __ptr, const __shared_count<_Lp>& __refcount) noexcept
{
if (use_count() == )
{
_M_ptr = __ptr;
_M_refcount = __refcount;
}
}

_M_enable_shared_from_this_with 函数在 shared_ptr<_Yp> 的构造函数中被调用, 从而检测 _Yp 是否继承自 make_shared_from_this, 并进行相应的处理. 这里的 _M_refcountshared_ptr 的成员, 用来记录 _Yp 被多少 shared_ptr 管理. 这样, 就完成了对 weak_ptr 的处理, 使其成为一个有效值. 在以后调用 shared_from_this() 函数时, 就能利用 weak_this 调用 shared_ptr 的构造函数, 从而生成一个共享同一对象的 shared_ptr.

上文部分代码参考了cppreference上的实例代码和gcc的源码

enable_shared_from_this类的作用和实现的更多相关文章

  1. boost库----enable_shared_from_this类的作用和实现原理

    使用boost库时,经常会看到如下的类 class A:public enable_share_from_this<A> 在什么情况下要使类A继承enable_share_from_thi ...

  2. C++虚基类的作用

    虚基类的作用     当一个基类被声明为虚基类后,即使它成为了多继承链路上的公共基类,最后的派生类中也只有它的一个备份.例如:class CBase { }:class CDerive1:virtua ...

  3. dubbo源码分析4——SPI机制_ExtensionFactory类的作用

    ExtensionFactory的源码: @SPI public interface ExtensionFactory { /** * Get extension. * * @param type o ...

  4. 关于boost中enable_shared_from_this类的原理分析

    首先要说明的一个问题是:如何安全地将this指针返回给调用者.一般来说,我们不能直接将this指针返回.想象这样的情况,该函数将this指针返回到外部某个变量保存,然后这个对象自身已经析构了,但外部变 ...

  5. 关于struts2中ActionContext类的作用

    关于struts2中ActionContext类的作用有三个: 1.获取三大作用域对象及页面参数 2.是struts标签的上下文对象 3.ThreadLocal内装的就是ActionContext 怎 ...

  6. JAVA基础加强(张孝祥)_类加载器、分析代理类的作用与原理及AOP概念、分析JVM动态生成的类、实现类似Spring的可配置的AOP框架

    1.类加载器 ·简要介绍什么是类加载器,和类加载器的作用 ·Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader ...

  7. Java中Class和单例类的作用与类成员的理解

    Java中Class类的作用与深入理解 在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识.这个信息跟踪着每个对象所属的类.JVM利用运行时信息选择相应的方法执行.而保存 ...

  8. 【转载】C#中SqlCommand类的作用以及常用方法

    在C#的数据库操作过程中,SqlCommand类一般用于Sqlserver数据库的SQL语句的执行,包括Select语句.Update语句.Delete语句以及SQL存储过程等,SqlCommand的 ...

  9. 【转载】C#中SqlConnection类的作用以及常用方法

    在C#的数据库编程中,SqlConnection类主要用于连接Sqlserver数据库,使用SqlConnection类的实例方法我们可以打开Sqlserver数据库连接以及获取数据完毕后关闭数据库连 ...

随机推荐

  1. vue框架-学习记录

    前段时间在做vue项目时,遇到挺多问题,想简单总结一下: 1.关于父组件,子组件的通信 网上有很多这方面的讲解,讲解也比较细致,我主要总结了自己在项目中需要的: [1]父组件-子组件 也就是" ...

  2. 基于jq的表单填充

    //表单填充 formDataLoad: function (domId, obj) { for (var property in obj) { if (obj.hasOwnProperty(prop ...

  3. 从flexible.js引入高德地图谈起的移动端适配

    曾几何时,前端还仅仅是PC端的.随着移动时代的兴起,h5及css3的推陈出新.前端的领域慢慢的由传统的pc端转入了移动端,这也导致了前端这一职业在风口的一段时间出尽了风头. 从开始的惶恐和无从下手,慢 ...

  4. iOS 添加WKWebView导致控制器无法释放的问题

    在WkWebView与JavaScript交互中,经常会在原生中注入MessageHandler,app中注入MessageHandler的方法 WKWebViewConfiguration *con ...

  5. 手把手教你树莓派实现简易室内监控系统(A)

    第一次写博文,有很多疏漏之处,然后受逼乎影响较深,希望大家多多包涵! _______________________________________________分割线是这样画的吧_________ ...

  6. C语言_指针变量的赋值与运算,很详细

    指针变量的赋值 指针变量同普通变量一样,使用之前不仅要定义说明, 而且必须赋予具体的值.未经赋值的指针变量不能使用, 否则将造成系统混乱,甚至死机.指针变量的赋值只能赋予地址, 决不能赋予任何其它数据 ...

  7. 震撼功能:逐浪CMS全面支持PWA移动生成意指未来

    Progressive Web App, 简称 PWA,是提升 Web App 的体验的一种新方法,能给用户原生应用的体验. PWA 能做到原生应用的体验不是靠特指某一项技术,而是经过应用一些新技术进 ...

  8. 《设计模式之禅》--备忘录扩展:clone方式的备忘录

    接上篇<设计模式之禅>--策略扩展:策略枚举 需求:使用clone方式实现备忘录模式 发起人角色 public class Originator implements Cloneable ...

  9. linux清屏命令(clear,reset)

    (1)clear 这个命令将会刷新屏幕,本质上只是让终端显示页向后翻了一页,如果向上滚动屏幕还可以看到之前的操作信息.一般都会用这个命令. (2)reset 这个命令将完全刷新终端屏幕,之前的终端输入 ...

  10. filebeat.yml(中文配置详解)

    ################### Filebeat Configuration Example ######################### ####################### ...