参考博客:

引言

在C++编程中,使用智能指针是一种安全管理对象生命周期的方式。std::shared_ptr是一种允许多个指针共享对象所有权的智能指针。然而,当一个对象需要获取对自身的shared_ptr时,传统的方法可能导致未定义行为。为了解决这个问题,C++引入了std::enable_shared_from_this类,本文将深入探讨其基础知识、使用案例以及内部实现。

首先抛出一些问题:

  1. enable_shared_from_this通常被用来做什么,什么场景被使用?
  2. enable_shared_from_this解决了什么问题?
  3. enable_shared_from_this的public、private继承为何需要特别注意,不然会引发什么问题?
  4. enable_shared_from_this内部的实现细节你知道多少呢?

std::shared_ptr基础知识

首先,我们回顾一下std::shared_ptr的基础知识。它是一种智能指针,通过共享控制块的方式安全地管理对象的生命周期。多个 shared_ptr 实例通过共享的 控制块 结构来控制对象的生命周期。

当使用原始指针构造或初始化 shared_ptr 时,会创建一个新的控制块。为了确保对象仅由一个共享控制块管理,对对象的任何额外的 shared_ptr 实例必须通过复制已经存在的指向该对象的 shared_ptr 来产生,例如:

void run() {
auto p{new int(12)}; //p 是 int*
std::shared_ptr<int> sp1{p};
auto sp2{sp1}; //OK sp2 与 sp1 共享控制块
}

使用原始指针初始化已经由 shared_ptr 管理的对象会创建另一个控制块来管理该对象,这将导致未定义的行为。例如:

void bad_run() {
auto p{new int(12)};
std::shared_ptr<int> sp1{p};
std::shared_ptr<int> sp2{p}; //! 未定义行为
}

从一个原始指针实例化多个 shared_ptr 是一种严重后果的编程失误。在可能的情况下,尽量使用 std::make_shared(或 std::allocate_shared)来减少发生此错误的可能性。例如:

auto sp1 = std::make_shared<int>();
std::shared_ptr<int> sp2{sp1.get()}; // 这会发生什么?

然而,有些情况下,shared_ptr 托管的对象需要获得一个指向自己的 shared_ptr。但首先,像下面这样尝试使用 this 指针创建 shared_ptr 不会起作用,原因如上所述:

struct Foo {
std::shared_ptr<Foo> getSelfPtr() {
return std::shared_ptr<Foo>(this);
}
//...
}; void run() {
auto sp1 = std::make_shared<Foo>();
auto sp2 = sp1->getSelfPtr(); //!! 未定义行为
/*sp1 和 sp2 有两个不同的控制块 管理相同的 Foo*/
}

这就是 std::enable_shared_from_this<T> 发挥作用的地方。公开继承 std::enable_shared_from_this 的类可以通过调用方法 shared_from_this() 获得指向自己的 shared_ptr。以下是它的一个基本示例:

#include <memory>

struct Foo : std::enable_shared_from_this<Foo> {
std::shared_ptr<Foo> getSelfPtr() {
return shared_from_this();
}
//...
}; void run() {
auto sp1 = std::make_shared<Foo>();
auto sp2 = sp1->getSelfPtr(); // OK
/*sp1 和 sp2 共享相同的控制块,正确管理 Foo*/
}

enable_shared_from_this类初识

std::enable_shared_from_this 的实现是一个类,它只包含一个 weak_ptr 字段(通常称为 _M_weak_this),这里面有很多细节:看看你知道吗?

  • _M_weak_this成员是在何时被初始化的,怎么初始化的?
  • friend class声明在这里起到了什么作用?
template <typename _Tp>
class 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); }
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;
};

这里的friend声明特别重要,这样的话,__shared_ptr便可以访问这个类的所有private成员,因此__shared_ptr便可以访问_M_weak_assign__enable_shared_from_this_base_M_weak_this

至于_M_weak_this 在什么地方被初始化见下方内容。

实现原理

假设此时Foo继承了enable_shared_from_this,当我们编写这样一段代码到底放生了什么?

于此同时,我们要解决第一个问题:为何enable_shared_from_this需要public继承,私有继承会发生什么?

auto sp1 = std::make_shared<Foo>();

make_shared会调用allocate_shared,随后调用shared_ptr的构造函数,再调用__shared_ptr的构造函数,此时我们可以看到会调用_M_enable_shared_from_this_with,它是一个模版函数,此时会使用ADL从enable_shared_from_this类中查找enable_shared_from_this

template<typename _Alloc, typename... _Args>
__shared_ptr(_Sp_alloc_shared_tag<_Alloc> __tag, _Args&&... __args)
: _M_ptr(), _M_refcount(_M_ptr, __tag, std::forward<_Args>(__args)...)
{ _M_enable_shared_from_this_with(_M_ptr); }

查找到了则拿到base,通过访问私有函数_M_weak_assign来初始化_M_weak_this。如果没查找到,则不会初始化_M_weak_this

当我们通过public继承enable_shared_from_this时,可以正常的初始化_M_weak_this,而如果是private继承,这里会走空实现,_M_weak_this未被初始化。

    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
{ }

make_shared看起来一切正常,那为啥我还要强调上面这些逻辑呢,往下看。

当我们使用了shared_from_this(),在private会报异常。

std::shared_ptr<Foo> getSelfPtr() { return shared_from_this(); }

shared_from_this()会基于已有的_M_weak_this构造shared_ptr,_M_refcount是一个__shared_count对象。

template<typename _Yp, typename = _Compatible<_Yp>>
explicit __shared_ptr(const __weak_ptr<_Yp, _Lp>& __r)
: _M_refcount(__r._M_refcount) // may throw
{
// ...
}

这里会使用_M_weak_this_M_refcount去初始化__shared_count,而当私有继承时,_M_weak_this并没有被初始化,于是引发了bad_weak_ptr异常。

template<_Lock_policy _Lp>
inline
__shared_count<_Lp>::__shared_count(const __weak_count<_Lp>& __r)
: _M_pi(__r._M_pi)
{
if (_M_pi != nullptr)
_M_pi->_M_add_ref_lock();
else
__throw_bad_weak_ptr();
}

灵魂拷问std::enable_shared_from_this,揭秘实现原理的更多相关文章

  1. 灵魂拷问:你真的理解System.out.println()执行原理吗?

    原创/朱季谦 灵魂拷问,这位独秀同学,你会这道题吗?  请说说,"System.out.println()"原理...... 这应该是刚开始学习Java时用到最多一段代码,迄今为止 ...

  2. 灵魂拷问:为什么 Java 字符串是不可变的?

    在逛 programcreek 的时候,发现了一些精妙绝伦的主题.比如说:为什么 Java 字符串是不可变的?像这类灵魂拷问的主题,非常值得深思. 对于绝大多数的初级程序员来说,往往停留在" ...

  3. 灵魂拷问:Java 的 substring() 是如何工作的?

    在逛 programcreek 的时候,我发现了一些小而精悍的主题.比如说:Java 的 substring() 方法是如何工作的?像这类灵魂拷问的主题,非常值得深入地研究一下. 另外,我想要告诉大家 ...

  4. 灵魂拷问:如何检查Java数组中是否包含某个值 ?

    在逛 programcreek 的时候,我发现了一些专注细节但价值连城的主题.比如说:如何检查Java数组中是否包含某个值 ?像这类灵魂拷问的主题,非常值得深入地研究一下. 另外,我想要告诉大家的是, ...

  5. 【灵魂拷问】你为什么要来学习Node.js呢?

    [灵魂拷问]你为什么要来学习Node.js呢? 学习node.js适合的人群: 需要必备一些HTML,CSS,JavaScript及编程有一定程度了解的读者阅读,一些简单的命令行操作,具备服务端开发经 ...

  6. 灵魂拷问:创建 Java 字符串,用""还是构造函数

    在逛 programcreek 的时候,我发现了一些小而精悍的主题.比如说:创建 Java 字符串,用 "" 还是构造函数?像这类灵魂拷问的主题,非常值得深入地研究一下. 01.& ...

  7. 灵魂拷问:Java对象的内存分配过程是如何保证线程安全的?(阿里面试)

    JVM内存结构,是很重要的知识,相信每一个静心准备过面试的程序员都可以清楚的把堆.栈.方法区等介绍的比较清楚. 上图,是一张在作者根据<Java虚拟机规范(Java SE 8)>中描述的J ...

  8. 工厂设计模式灵魂拷问-Java实现

    show me the code and take to me,做的出来更要说的明白 GitHub项目JavaHouse同步收录 喜欢就点个赞呗! 你的支持是我分享的动力! 引入 我们经常听到工厂模式 ...

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

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

  10. [灵魂拷问]MySQL面试高频100问(工程师方向)

    作者:呼延十 juejin.im/post/5d351303f265da1bd30596f9 前言 本文主要受众为开发人员,所以不涉及到MySQL的服务部署等操作,且内容较多,大家准备好耐心和瓜子矿泉 ...

随机推荐

  1. Mockito - java单元测试

    原文地址 一.简介 Mockito是mocking框架,它让你用简洁的API做测试,简单易学,可读性强并且验证语法简洁. 官网: http://mockito.org 项目源码:https://git ...

  2. 一套开源、强大且美观的WPF UI控件库 - HandyControl

    前言 今天给大家推荐一套开源.强大且美观的WPF UI控件库:HandyControl. WPF介绍 WPF 是一个强大的桌面应用程序框架,用于构建具有丰富用户界面的 Windows 应用.它提供了灵 ...

  3. 一篇文章带你掌握Web自动化测试工具——Selenium

    一篇文章带你掌握Web自动化测试工具--Selenium 在这篇文章中我们将会介绍Web自动化测试工具Selenium 如果我们需要学习相关内容,我们需要掌握Python,PyTest以及部分前端知识 ...

  4. [复习随笔]python_dcgan网络复习小知识:模型定义

    定义参数 dataroot - the path to the root of the dataset folder. We will talk more about the dataset in t ...

  5. [ABC274F] Fishing

    Problem Statement On a number line, there are $N$ fish swimming. Fish $i$, which has a weight of $W_ ...

  6. 后端程序员必会的前端知识-04:Vue3

    Vue 3 1. TypeScript 1) 动态类型的问题 前面我们讲过 js 属于动态类型语言,例如 function test(obj) { } obj 可能只是个字符串 test('hello ...

  7. 聊聊流式数据湖Paimon(三)

    概述 如果表没有定义主键,则默认情况下它是仅追加 表类型(Append Only Table). 根据桶(Bucket)的定义,我们有两种不同的仅追加模式:"Append For Scala ...

  8. 春秋云镜 - CVE-2022-32991

    靶标介绍: 该CMS的welcome.php中存在SQL注入攻击. 访问页面,先注册,使用邮箱加密码登录. bp抓包,后台挂上sqlipy然后去测welcome.php,常用的语句都没成功但过一会就有 ...

  9. HTB - CozyHosting - WriteUp

    CozyHosting 前言:抓紧赛季末上一波分,错过开vip才能练了 信息收集 扫描看看端口的开放情况,开了22,80,5555.这里fscan显示会跳转到cozyhosting.htb. 那就需要 ...

  10. VSFTPD2.3.4(笑脸漏洞)复现

    vsftpd2.3.4笑脸漏洞复现 目标服务器:metasploitable2(192.168.171.11) 渗透机:Kali(192.168.171.21) 方法一:手动复现 首先用kali扫描一 ...