(C++) 内类生成智能指针shared_from_this介绍

文章目录

  • 😁介绍
  • 🤔类外操作
    • 😅错误操作
    • 😂正确操作
  • 🤔类内操作
    • 😮std::enable_shared_from_this<>
      • 😮奇异递归模板 CRTP(Curiously Recurring Template Pattern)
      • 😮基本原理
      • 😮std::enable_shared_from_this<>
    • 😅错误操作
      • 😅shared_ptr 经典错误
      • 😅enable_shared_from_this 经典错误
    • 😂正确操作
  • 😁END

😁介绍

自C++11起,有三大智能指针:

  • unique_ptr
  • shared_ptr
  • weak_ptr

都是内存管理中的非常重要的一部分动态内存管理 - cppreference.com。

其中shared_ptr在实际应用中具有非常广泛的应用。而拷贝操作也是非常常见和重要的操作。在类外可以直接使用默认的拷贝构造和拷贝赋值,而类内呢?显然这两种默认的拷贝操作均不适用。

本文就是讲解使用std::enable_shared_from_this::shared_from_this()来处理该问题。

🤔类外操作

😅错误操作

使用原始指针构造。

这是一种非常经典的错误。虽然构造出的智能指针都被多个smart_ptr掌管了,但是并非同一个控制块来操作,是随着每次构造都有一个控制块。

因此下方示例代码会产生两次析构。严重情况下可以让项目程序直接崩溃。

#include <iostream>
#include <memory>struct Node {~Node() {std::printf("[%p] %s\n", this, __func__);}
};void test(Node* p) {// errro 同一个对象析构了两次std::shared_ptr<Node> sp1(p);std::shared_ptr<Node> sp2(p);
}int main() {Node* p = new Node{};test(p);std::printf("[%u] %s end\n", __LINE__, __func__);
}

😂正确操作

直接使用拷贝操作。

对于shared_ptr默认的拷贝操作,可以由多个shart_ptr都指向同一个对象,并由同一个控制块维护。

#include <iostream>
#include <memory>struct Node {~Node() {std::printf("[%p] %s\n", this, __func__);}
};void test(Node* p) {std::shared_ptr<Node> sp1(p);// 执行拷贝构造,同时维护同一个pstd::shared_ptr<Node> sp2 = sp1;
}int main() {Node* p = new Node{};test(p);std::printf("[%u] %s end\n", __LINE__, __func__);
}

🤔类内操作

😮std::enable_shared_from_this<>

😮奇异递归模板 CRTP(Curiously Recurring Template Pattern)

CRTP是一种模板元编程技术,它允许一个类模板通过基类的形式捕获自身的类型。

这种模式的名字来源于其自身的递归使用,“Curiously” 指的是这种模式的非直观性,因为在通常情况下,基类通常是在类定义之后才被引用的,而在这个模式中,基类是在类定义的开始就引用了尚未完全定义的类。

😮基本原理

CRTP的基本原理是利用模板类和继承。在CRTP中,一个类通过继承一个模板类,并将自身作为模板参数传递给基类模板。这样做的好处是可以在派生类中使用基类模板中的成员,而这些成员依赖于派生类的类型。

😮std::enable_shared_from_this<>

std::enable_shared_from_this 能让其一个对象(假设其名为 t,且已被一个 std::shared_ptr 对象 pt 管理)安全地生成其他额外的 std::shared_ptr 实例(假设名为 pt1, pt2, ...),它们都与 pt 共享对象 t 的所有权。

其中有两个核心函数:

  • shared_from_this
  • weak_from_this()
#include <memory>
// 奇异递归模板 CRTP(Curiously Recurring Template Pattern)
struct Node : public std::enable_shared_from_this<Node> { };

😅错误操作

😅shared_ptr 经典错误

还是先展示一个经典错误。

由于在类内的成员函数,(没有额外操作的情况下)只能访问到this指针。

#include <iostream>
#include <memory>struct Node {~Node() {std::printf("[%p] %s\n", this, __func__);}std::shared_ptr<Node> make_this_share_ptr() {// 经典错误return std::shared_ptr<Node>(this);}
};void test(Node* p) {std::shared_ptr<Node> sp1 = p->make_this_share_ptr();std::shared_ptr<Node> sp2 = p->make_this_share_ptr();
}int main() {Node* p = new Node{};test(p);std::printf("[%u] %s end\n", __LINE__, __func__);
}

😅enable_shared_from_this 经典错误

回到本文的重头戏,这里使用奇异递归模板的方式继承std::enable_shared_from_this<>,并使用shared_from_this()创建出指向自己的share_ptr。

但如果直接使用,还是会出现一个经典错误。

#include <iostream>
#include <memory>struct Node : public std::enable_shared_from_this<Node> {~Node() {std::printf("[%p] %s\n", this, __func__);}std::shared_ptr<Node> make_this_share_ptr() {return shared_from_this();}
};void test(Node* p) {// 事先没有创建任何`std::shared_ptr`实例std::shared_ptr<Node> sp = p->make_this_share_ptr();
}int main() {Node* p = new Node{};test(p);std::printf("[%u] %s end\n", __LINE__, __func__);
}

即:在没有使用构造出std::shared_ptr,就直接调用了shared_from_this(),会抛出异常。

ref: 若在先前未由 std::shared_ptr 共享的对象上调用 shared_from_this,则抛出 std::bad_weak_ptr

在mingw-gcc中输出如下:

terminate called after throwing an instance of 'std::bad_weak_ptr'
what():  bad_weak_ptr

😂正确操作

正确操作即为先构造出一个std::shared_ptr再使用shared_from_this()

保证了前提安全后,不论是std::shared_ptr还是裸指针去操作shared_from_this()都不会出现问题。

#include <iostream>
#include <memory>struct Node : public std::enable_shared_from_this<Node> {~Node() {std::printf("[%p] %s\n", this, __func__);}std::shared_ptr<Node> make_this_share_ptr() {return shared_from_this();}
};void test(Node* p) {// safestd::shared_ptr<Node> sp0(p);std::shared_ptr<Node> sp1 = sp0;std::shared_ptr<Node> sp2 = sp1->make_this_share_ptr();std::shared_ptr<Node> sp3 = p->make_this_share_ptr();
}int main() {Node* p = new Node{};test(p);std::printf("[%u] %s end\n", __LINE__, __func__);
}

😁END

ref

  • std::shared_ptr - cppreference.com
  • std::enable_shared_from_this - cppreference.com

关注我

关注我,学习更多C/C++,算法,计算机知识

B站:

👨‍💻主页:天赐细莲 bilibili

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/3188.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

carrier开利触摸屏ICVC控制面板维修CEPL130445

开利离心机19XR空调ICVC显示面板维修CEPL130445-03-R/04-R/02-R 人机界面触摸维修故障有&#xff1a;花屏、白屏、按触摸屏无反应或反应慢(触摸不好)、内容错乱、无背光、背光暗、有背光无字符、不能通信、按键无反应等&#xff08;可更换液晶屏&#xff09;黑屏、对触摸屏触摸…

QML 中的状态

Qt hello – 专注于Qt的技术分享平台 状态描述了当前用户界面样子&#xff0c;QML中一个状态定义了一组属性的改变&#xff0c;并且会在一定条件下被触发。 假设有这么一个场景&#xff0c;红黄绿三个灯&#xff0c;用一个按钮&#xff0c;点击后依次切换三个灯亮起。使用QWi…

js如何获取对象的属性值

获取对象的属性值&#xff0c;有两种方式。 方式一&#xff1a; 对象.属性名 let obj {name:张三,age:23 }; console.log(obj.name); //张三方式二&#xff1a; 对象[属性名] let obj {name:张三,age:23 }; console.log(obj[name]); //张三 两种方式有什么不同&am…

AWR报告采集

一、windows下采集 自动负载信息库&#xff08;Automatic Workload Repository&#xff0c;AWR&#xff09;是在Oracle 10g中被引入的&#xff0c;缺省地被安装到Oracle10g数据库中&#xff0c;用于收集关于该特定数据库的操作统计信息和其他统计信息。AWR的采样工作由后台进程…

Linux-线程互斥和死锁

目录 一.线程互斥 1.1 进程线程间的互斥相关背景概念 1.2 互斥量mutex 二.互斥量的接口 2.1 初始化互斥量 2.2 销毁互斥量 2.3 互斥量加锁和解锁 2.4 改进后售票代码 三.死锁 3.1.什么是死锁&#xff1f; 3.2.死锁四个必要条件 3.3 避免死锁 一.线程互斥 1.1 进程…

吴恩达2022机器学习专项课程(一) 6.1 动机第三周课后实验:Lab1使用逻辑回归进行分类

问题预览/关键词 什么是逻辑回归&#xff1f;什么是二分类问题&#xff1f;二分类问题案例如何表达二分类的结果&#xff1f;逻辑回归通常用哪种表达形式&#xff1f;什么是正样本和负样本&#xff1f;什么是阈值&#xff1f;可视化线性回归解决二分类线性回归面对二分类产生的…

jmeter之跨线程关联

1&#xff09;_setproperty函数&#xff1a;将值保存成jmeter属性 2&#xff09;_property函数&#xff1a;在其他线程组中使用property函数读取属性 一、跨线程接口引用变量 1. 法一&#xff1a;jmeter自带函数_setProperty和_property 1. 1线程组 01 创建登录的【HTTP请求】…

1、k8s问题pod从service中剔除

一、起因 redis原来由两服务器的集群变为三服务器的集群&#xff0c;通过statefulset扩展了两节点&#xff0c;并把redis-app-0和redis-app-3从集群中去除&#xff0c;但是由于service路由后端不变&#xff0c;导致程序连接后端仍然可能到redis-app-0和redis-app-3 二、处理 …

分类算法——决策树(五)

认识决策树 决策树思想的来源非常朴素&#xff0c;程序设计中的条件分支结构就是if-else结构&#xff0c;最早的决策树就是利用这类结构分割数据的一种分类学习方法。 决策树分类原理详解 为了更好理解决策树具体怎么分类的&#xff0c;通过一个问题例子&#xff1a; 问题…

C++入门----内联函数auto范围fornullptr指针

1.内联函数 顾名思义&#xff0c;内联函数也是函数的一种&#xff0c;我们在C语言的学习过程里面知道了函数和宏之间的区别和各自的优缺点&#xff1b; 函数的使用需要建立栈帧&#xff0c;宏的使用需要考虑各种符号的优先级问题&#xff0c;很容易出错&#xff0c;因为宏在使…

HarmonyOS开发案例:【闹钟】

介绍 使用后台代理提醒&#xff0c;实现一个简易闹钟。要求完成以下功能&#xff1a; 展示指针表盘或数字时间。添加、修改和删除闹钟。展示闹钟列表&#xff0c;并可打开和关闭单个闹钟。闹钟到设定的时间后弹出提醒。将闹钟的定时数据保存到轻量级数据库。 相关概念 [Canva…

数学建模完整版

模型与适用题型 微分方程传染病预测模型 神经网络 层次分析法 粒子群算法 matlab 优劣解距离法

如何为Postgres数据库设置安全的访问控制和权限管理

文章目录 解决方案1. 使用角色和权限管理2. 配置认证方法3. 使用网络访问控制4. 定期审查和更新权限 示例代码1. 创建角色并分配权限2. 配置密码认证3. 配置网络访问控制 总结 PostgreSQL是一个功能强大的开源关系型数据库系统&#xff0c;提供了丰富的权限和访问控制机制&…

LLM大语言模型(十二):关于ChatGLM3-6B不兼容Langchain 的Function Call

背景 基于本地的ChatGLM3-6B直接开发LangChain Function Call应用&#xff0c;发现其输出的action和action_input非常不稳定。 表现为生成的JSON格式回答非常容易出现不规范的情况&#xff0c;导致LangChain的Agent执行报错&#xff0c;或者进入死循环。 ChatGLM3-6B不兼容La…

基于北京迅为iTOP-RK3588大语言模型部署测试

人工智能&#xff08;AI&#xff09;领域中的大模型&#xff08;Large Model&#xff09;逐渐成为研究的热点。大模型&#xff0c;顾名思义&#xff0c;是指拥有海量参数和高度复杂结构的深度学习模型。它的出现&#xff0c;不仅推动了AI技术的突破&#xff0c;更为各行各业带来…

【C++】——类和对象(构造函数,析构函数,拷贝构造函数,赋值运算符重载)

创作不易&#xff0c;多多支持&#xff01; 前言 相信你对这几个知识点有点混淆&#xff0c;相信看完以后&#xff0c;你会对此有一个清晰的认识。 一 类的6个默认成员函数 如果我们写一个类&#xff0c;但是类里面什么都没有&#xff0c;我们称之为空类。 其实这个类也不…

DC-DC电源芯片规格书上的各种参数详解

1.输出电压精确度 输出电压的精确度,也被称为设定点精度,它描述了输出电压的允许误差。该参数通常是在常温,满载和额定输入电压的条件下测得的,它是这样定义的: 输出电压之所以产生误差,是因为元器件本身存在误差,特别是输出端的分压电阻,它将输出电压降低后比PWM比较…

Mac装虚拟机好不好 Mac装虚拟机和装Windows系统一样吗 PD虚拟机

随着跨系统操作的不断发展&#xff0c;虚拟机技术在生产力领域扮演着越来越重要的角色。Mac作为一款主流的操作系统&#xff0c;也有着运行虚拟机的能力。接下来给大家介绍Mac装虚拟机好不好&#xff0c;Mac装虚拟机和装Windows系统一样吗的具体内容。 一、Mac装虚拟机好不好 …

UNIX环境高级编程->高级IO(概念篇)

UINX环境 常见的操作系统比如Linux 和 MacOS都是基于UNIX的。它们都继承了UNIX的许多特性和设计理念。因此&#xff0c;它们的高级I/O技术确实是建立在UNIX基础之上的。Linux和macOS都提供了丰富的高级I/O功能和API&#xff0c;利用了UNIX系统调用和原则。这些功能包括非阻塞I…

【Linux】自定义协议——实现网络序列化和反序列化

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;折纸花满衣 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;承接上文内容【Linux】应用层协议序列化和反序列化 目录 &#x1f449;&#x1f3fb;代码实现如下Calculate.hp…