何时/如何使用 std::enable_shared_from_this

std,enable,shared,from,this · 浏览次数 : 0

小编点评

本文介绍了C++标准库中的`std::enable_shared_from_this`类及其作用。`std::enable_shared_from_this`是一个模板类,继承自`std::enable_shared_from_this`允许一个对象t由一个`std::shared_ptr`pt管理的同时,安全地创建指向自身的其他`std::shared_ptr`实例。 本文首先解释了`std::enable_shared_from_this`的作用和必要性,然后通过一个处理器类的例子说明了如何使用`std::enable_shared_from_this`。接着,文章详细讨论了`std::enable_shared_from_this`的内部实现,并给出了一个修改后的处理器类实现示例。最后,总结了`std::enable_shared_from_this`的主要概念、用途以及内部实现原理。

正文

要点回顾


  • 继承自 std::enable_shared_from_this<T> 的类能够在其自身实例中通过 std::shared_from_this 方法创建一个指向自己的 std::shared_ptr<T> 智能指针。
  • 从一个裸指针创建多个 std::shared_ptr<T> 实例会造成严重的后果,其行为是未定义的。
  • std::enable_shared_from_this<T> 实际包含了一个用于指向对象自身的 std::weak_ptr<T> 指针。

引言


本文介绍 std::enable_shared_from_thisstd::shared_from_this 的基本概念和使用方法。

定义 "std::enable_shared_from_this"


以下内容是 cppreference.com 上关于 std::enable_shared_from_this 的定义和说明:

Defined in header < memory >
template< class T > class enable_shared_from_this; (since C++11)

std::enable_shared_from_this allows an object t that is currently managed by a std::shared_ptr named pt to safely generate additional std::shared_ptr instances pt1, pt2, ... that all share ownership of t with pt.

Publicly inheriting from std::enable_shared_from_this<T> provides the type T with a member function shared_from_this. If an object t of type T is managed by a std::shared_ptr<T> named pt, then calling T::shared_from_this will return a new std::shared_ptr<T> that shares ownership of t with pt.

简单来说就是,继承自 std::enable_shared_from_this<T> 的类能够在其自身实例中通过 std::shared_from_this 方法创建一个指向自己的 std::shared_ptr<T> 智能指针。

想要理解 std::enable_shared_from_this<T>,首先得知道为什么需要 std::enable_shared_from_this<T>,请看下文。


使用 "std::enable_shared_from_this"


为什么需要 std::enable_shared_from_this<T>? 我们从一个例子讲起,会更容易一些。

假定有一个类 Processor, 它的作用是异步处理数据并且存储到数据库。当 Processor 接收到数据时,它通过一个定制的 Executor 类型来异步处理数据:

class Executor {
public:
 //Executes a task asynchronously
 void
 execute(const std::function<void(void)>& task);
 //....
private:
 /* Contains threads and a task queue */
};

class Processor {
public:
 //...
 //Processes data asynchronously. (implemented later)
 void processData(const std::string& data); 

private:
 //Called in an Executor thread 
 void doProcessAndSave(const std::string& data) {
    //1. Process data
    //2. Save data to DB
 }
 //.. more fields including a DB handle..
 Executor* executor;
};

Client 类包含了一个 std::shared_ptr<Processor> 实例,Processor 从 Client 类接收数据:

class Client {
public:
 void someMethod() {
  //...
  processor->processData("Some Data");
  //..do something else
 }
private:
 std::shared_ptr<Processor> processor;
};

以上示例中,Executor 是一个线程池,用于执行任务队列中的任务。
Processor::processData 中,我们需要传递一个(指针)函数(lambda 函数)给 Executor 来执行异步操作。该 lambda 函数调用 Processor::doProcessAndSave 以完成实际的数据处理工作。因此,该 lambda 函数需要捕获一个 Processor 对象的引用/指针。我们可以这样做:

void Processor::processData(const std::string& data) {
 executor->execute([this, data]() { //<--Bad Idea
   //Executes in an Executor thread asynchronously
   //'this' could be invalid here.
   doProcessAndSave(data);
 });
}

然而,因为种种原因,Client 可能会随时重置 std::shared_ptr<Processor>,这可能导致 Processor 的实例被析构。因此,在执行 execute 函数时或者在执行之前,上述代码中捕获的 this 指针随时可能会变为无效指针。

怎么办?

我们可以通过在 lambda 函数中捕获并保留一个指向当前对象本身的 std::shared_ptr<Processor> 实例来防止 Processor 对象被析构。

下图展示了示例代码的交互逻辑:

那么,在 Processor 实例中通过 shared_ptr(this) 创建一个智能指针呢?其行为是未定义的!

std::shared_ptr<T> 允许我们安全地访问和管理对象的生命周期。多个 std::shared_ptr<T> 实例通过一个共享控制块结构(a shared control block structure)来管理对象的生命周期。一个控制块维护了一个引用计数,及其他必要的对象本身的信息。

void good() {
 auto p{new int(10)}; //p is int*
 std::shared_ptr<int> sp1{p}; 
 //Create additional shared_ptr from an existing shared_ptr
 auto sp2{sp1}; //OK. sp2 shares control block with sp1
}

从一个裸指针创建一个 std::shared_ptr<T> 会创建一个新的控制块。从一个裸指针创建多个 std::shared_ptr<T> 实例会造成严重的后果:

void bad() {
 auto p{new int(10)};   
 std::shared_ptr<int> sp1{p};
 std::shared_ptr<int> sp2{p}; //! Undefined Behavior
}

因此,我们需要一个机制能够达到我们的目的(捕获并保留一个指向当前对象本身的 std::shared_ptr<Processor> 实例)。

这就是 std::enable_shared_from_this<T> 存在的意义,以下是修改后的类 Processor 实现:

class Processor : public std::enable_shared_from_this<Processor> {
 //..same as above...
}; 

void Processor::processData(const std::string& data) {
 //shared_from_this() returns a shared_ptr<Processor> 
 //  to this Processor
 executor->execute([self = shared_from_this(), data]() { 
   //Executes in an Executor thread asynchronously
   //'self' is captured shared_ptr<Processor>
   self->doProcessAndSave(data); //OK. 
 });
}

self = shared_from_this() 传递的是一个合法的 std::shared_ptr<Processor> 实例,合法的类 Processor 对象的引用。


深入 "std::enable_shared_from_this" 内部


std::enable_shared_from_this<T> 的实现类似:

template<class T>
class enable_shared_from_this {
 mutable weak_ptr<T> weak_this;
public:
 shared_ptr<T> shared_from_this() {
  return shared_ptr<T>(weak_this); 
 }
 //const overload
 shared_ptr<const T> shared_from_this() const {
  return shared_ptr<const T>(weak_this); 
 }

 //..more methods and constructors..
 //there is weak_from_this() also since C++17

 template <class U> friend class shared_ptr;
};

enable_shared_from_this 包含了一个 std::weak_ptr<T> 指针,这正是函数 shared_from_this 返回的内容。注意,为什么不是 std::shared_ptr<T>? 因为对象包含自身的计数引用会导致对象永远不被释放,从而发生内存泄漏。上述代码中 weak_this 会在类对象被 std::shared_ptr<T> 引用时赋值,也就是std::shared_ptr<T> 实例的构造函数中赋值,这也是为什么类 enable_shared_from_this 的最后,其被声明成为了 shared_ptr 的友元。


总结


到此,关于 std::enable_shared_from_this<T> 的介绍就结束了。


引用


https://en.cppreference.com/w/cpp/memory/enable_shared_from_this
https://www.nextptr.com/tutorial/ta1414193955/enable_shared_from_this-overview-examples-and-internals

与何时/如何使用 std::enable_shared_from_this?相似的内容:

何时/如何使用 std::enable_shared_from_this

要点回顾 继承自 std::enable_shared_from_this 的类能够在其自身实例中通过 std::shared_from_this 方法创建一个指向自己的 std::shared_ptr 智能指针。 从一个裸指针创建多个 std::shared_ptr 实例会造成严

跨越HTTP无状态边界:Cookie与Session在Django中的实战应用

**本文深入探索了Django中的Cookie和Session,解析了如何应对HTTP协议的无状态性问题,说明其基础概念,分析工作原理,并讨论何时应选择使用Cookie或Session。文章进阶部分,提出高效管理Cookie和Session,以及如何利用它们进行用户身份验证。** ## HTTP协议

算法金 | 必会的机器学习评估指标

构建机器学习模型的关键步骤是检查其性能,这是通过使用验证指标来完成的。 选择正确的验证指标就像选择一副水晶球:它使我们能够以清晰的视野看到模型的性能。 在本指南中,我们将探讨分类和回归的基本指标和有效评估模型的知识。 学习何时使用每个指标、优点和缺点以及如何在 Python 中实现它们 1 分类指标

前端如何用一个函数防止用户使用F12看控制台(超简单一学就会)

#### 先分享一下自己的搭的免费的chatGPT网站 https://www.hangyejingling.cn/ ## 正文 #### 1、如果是VUE框架开发,在生产环境中。在入口文件APP.vue中添加如下代码,其他框架同理 ```javascript if (process.env.mod

每日一题:如何判断是否是数组,一个既简单又复杂的问题。(不要再用Object.prototype.toString.call、instance of判断了!!!)

1、不要使用Object.prototype.toString.call() 正常情况下: const arr = [1,2,3,4,5] const obj = {} console.log(Object.prototype.toString.call(arr))//[Object,Array]

[apue] 一图读懂 Unix 时间日期例程相互关系

GMT 和 UTC 时间有何区别?Unix 时间例程为何不处理闰秒?系统时区是如何设置的?哪些时间例程受夏时制影响?localtime 和 gmtime 是否共享内部存储区?strftime 获取第几周使用的 %U/%V/%W 有何区别?linux date 和 mac date 语法有何区别?本文一一为你解答

使用Electron-builder将web项目封装客户端安装包 发布

背景:之前用electron-packager将web项目打包成客户端时,exe文件只能在当前文件夹下运行,如果发送给别人使用 极不方便。所以我们可以用electron-builder将web项目封装成安装包给别人使用。 1、配置npm代理 npm set electron_mirror=https

[转帖]十二、G1垃圾收集器

G1收集器是一款面向服务器的垃圾收集器,也是HotSpot在JVM上力推的垃圾收集器,并赋予取代CMS的使命。为什么对G1收集器给予如此高的期望呢?既然对G1收集器寄予了如此高的期望,那么他一定是有特别之处。他和其他的垃圾收集器有何不同呢?下面我们将从以下几个方面研究G1收集器。 一、为什么会诞生G

面试官:Java类是如何被加载到内存中的?

面试连环call Java类是如何被加载到内存中的? Java类的生命周期都有哪些阶段? JVM加载的class文件都有哪些来源? JVM在加载class文件时,何时判断class文件的格式是否符合要求? 类生命周期 一个类从被加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期将会经历加载、验

处理机调度与死锁

一、处理机调度的层次 概念 按什么原则分配CPU:调度算法。 何时分配CPU:调度时机。 如何分配CPU:调度过程。 周转时间:完成时间-进入时间。(注意:从进入系统到执行完成包括在后备队列中等待调度、在就绪队列中等待进程调度、执行以及等待I/O操作完成四部分时间,作业进入是指作业准备好被调度的状态