C++之shared_from_this用法以及类自引用this指针陷阱

2023-11-04

C++系列文章目录



前言

shared_ptr实现原理
shared_ptr 从 _Ptr_base 继承了 element_type 和 _Ref_count_base 类型的两个成员变量。

template<class _Ty>class _Ptr_base
{ 
private: 
        element_type * _Ptr{
            ptr
        }; // 指向资源的指针 
        _Ref_count_base * _Rep{
            ptr
        }; // 指向资源引用计数的指针
};

_Ref_count_base 中定义了原子类型的变量 _Uses 和 _Weaks,它们分别记录资源的引用个数和资源观察者的个数。

class __declspec(novtable) _Ref_count_base
{ 
    private:
         _Atomic_counter_t _Uses;//记录资源引用个数 
         _Atomic_counter_t _Weaks;//记录观察者个数
}

当要使用 shared_ptr 管理同一资源,调用 shared_ptr 的构造函数和拷贝构造函数是不一样的,它们虽然使得不同 shared_ptr 指向同一资源,但管理引用计数资源的方式却不一样。


一、为什么需要enable_shared_from_this

下面例子有什么问题?

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <memory>
using namespace std;
 
int main(){
	shared_ptr<int> ptr1(new int(101)); 
	shared_ptr<int> ptr2(ptr1.get());//ptr1.get()返回ptr1保存对象的裸指针,ptr2会有新的计数器,不跟ptr1共享计数器
	cout << ptr1.use_count() << " " << ptr2.use_count() << " " << endl;
	//所以一定不要既用shared_ptr又用对象裸指针(p.get()相当于裸指针)即输出1 1
	cin.get();
	return 0;
}

在这里插入图片描述
通过上面的例子发现引用计数各自维护的是1,当两个对象ptr1、ptr2释放的时候会调用两次释放资源。

下面的例子是智能指针的正确用法:

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <memory>
using namespace std;
 
int main(){
	shared_ptr<int> ptr1(new int(101)); 
	shared_ptr<int> ptr2(ptr1);              //ptr1与ptr2共享引用技术2
	cout << ptr1.use_count() << " " << ptr2.use_count() << " " << endl;
	cin.get();
	return 0;
}

在这里插入图片描述
有时候自定义的类对象,需要自己引用计数,std::shared_ptr< T > (this),此时对象的引用计数也是各自维护一个计数1,因此是错误的用法,为了解决这个问题,引出来了 enable_shared_from_this。即当如果出现两个shared_ptr指针都指向同一对象,但是计数器不共享时,会导致对象被释放两次,程序出错了,就如同上面的例子。

为什么需要enable_shared_from_this
问题:为什么要这么麻烦,要继承一个继承enable_shared_from_this< T >模板对象,使用其中的成员函数shared_from_this(),直接使用:std::shared_ptr< TestA > getSharedFromThis() { return std::shared_ptr< TestA > (this); }

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <memory>
 
using namespace std;
 
class TestB
{
public:
	TestB(){
		cout << "TestB create" << endl;
	}
	~TestB(){
		cout << "TestB destory" << endl;
	}
	shared_ptr<TestB> getSharedFromThis() { 
		return  shared_ptr<TestB> (this); 
	}
};
int main(){
	{
		shared_ptr<TestB> ptr3(new TestB());
		shared_ptr<TestB> ptr4 = ptr3->getSharedFromThis();
		cout << "ptr2 count: " << ptr3.use_count() << " ptr4 count: " << ptr4.use_count() << endl;
		//输出:ptr2 count: 1 ptr4 count: 1 然后会崩溃因为重复释放
	}
	cin.get();
	return 0;
}

两个shared_ptr的引用计数都是1,然后释放时,导致对象被释放两遍,导致程序崩溃。
为什么会这样,要从shared_ptr的原理说起;shared_ptr为什么能够在没有shared_ptr指针指向对象时释放对象?
因为所有指向同一个对象的shared_ptr指针共享同一个计数器,当有新的shared_ptr指向对象时,计数器+1,有shared_ptr销毁或者不再指向该对象时,计数器-1,当计数器为0时,对象被销毁。

如何会导致shared_ptr指向同一个对象,但是不共享引用计数器。是因为裸指针与shared_ptr混用,如果我们用一个裸指针初始化或者赋值给shared_ptr指针时,在shared_ptr内部生成一个计数器,当另外一个shared_ptr不用share_ptr赋值或者初始化的话,再次将一个裸指针赋值给另外一个shared_ptr时,又一次生成一个计数器,两个计数器不共享。

二、enable_shared_from_this用法

c++11中的智能指针源于boost,所以也将enable_shared_from_this和其成员函数shared_from_this()也给收编了。通过模板方式继承enable_shared_from_this ,然后调用shared_from_this()函数返回对象T的shared_ptr指针,非常方便。
使用时需要引用头文件 :#include < memory >

enable_shared_from_this例子

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <memory>
using namespace std;
class TestA : public enable_shared_from_this<TestA>
{
public:
	TestA(){
		cout << "TestA create" << endl;
	}
	~TestA(){
		cout << "TesA destory" << endl;
	}
	shared_ptr<TestA> getSharedFromThis() { 
		return shared_from_this(); 
	}
};
 
int main(){
	{//出了此作用域 ptr1 ptr2 销毁, TestA对象销毁
		shared_ptr<TestA> ptr1(new TestA());
		shared_ptr<TestA> ptr2 = ptr1->getSharedFromThis();
		cout << "ptr1 count: " << ptr1.use_count() << " ptr2 count: " << ptr2.use_count() << endl;
		//输出:ptr1 count: 2 ptr2 count: 2  可以正常释放对象
	}
	return 0;
}

总结

授之以鱼不如授之以渔!!
如果大家喜欢,请三连!!!!!!!!!!!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++之shared_from_this用法以及类自引用this指针陷阱 的相关文章

  • C# 方法重载决策不选择具体的泛型覆盖

    这个完整的 C 程序说明了这个问题 public abstract class Executor
  • 使用 CMake 时如何导出 Emscripten 中的 C 函数

    In 本教程 https emscripten org docs porting connecting cpp and javascript Interacting with code html interacting with code
  • Grpc - 将消息从一个客户端发送到连接到同一服务器的另一个客户端

    是否可以将消息从一个客户端发送到连接到同一服务器的另一个客户端 我想将数据从一个客户端发送到服务器然后发送到特定客户端 我想我需要获取客户端 ID 但我不知道如何获取此 ID 以及如何从服务器将此消息发送到该客户端 我这里有一个样本 这是一
  • 现代 C++ 编译器是否能够在某些情况下避免调用 const 函数两次?

    例如 如果我有以下代码 class SomeDataProcessor public bool calc const SomeData d1 const SomeData d2 const private Some non mutable
  • 未找到 Boost 库,但编译正常

    我正在尝试在 C 中使用 boost 的文件系统 使用时看起来编译没问题 c c Analyse c o Analyse o g W Wall L usr local lib lboost filesystem lboost system
  • java中如何重新初始化int数组

    class PassingRefByVal static void Change int pArray pArray 0 888 This change affects the original element pArray new int
  • extern 声明和函数定义都在同一文件中

    我只是浏览了一下gcc源文件 在gcc c 我发现了类似的东西 extern int main int char int main int argc char argv 现在我的疑问是extern是告诉编译器特定的函数不在这个文件中 但可以
  • 如何将 .txt 文件中的数据转换为 xml? C#

    我在一个文本文件中有数千行数据 我想通过将其转换为更容易搜索的内容来轻松搜索 我希望 XML 或其他类型的大型数据结构 尽管我不确定它是否是最好的对于我的想法 每行的数据如下所示 第 31 册 托马斯 乔治 32 34 154 每本书都不是
  • 强制初始化模板类的静态数据成员

    关于模板类的静态数据成员未初始化存在一些问题 不幸的是 这些都没有能够帮助我解决我的具体问题的答案 我有一个模板类 它有一个静态数据成员 必须为特定类型显式实例化 即必须专门化 如果不是这种情况 使用不同的模板函数应该会导致链接器错误 这是
  • cpp.react库的C++源代码中奇怪的“->* []”表达式

    这是我在文档中找到的 C 片段cpp react 库 https github com schlangster cpp react implicit parallelism auto in D MakeVar 0 auto op1 in g
  • C++中判断unicode字符是全角还是半角

    我正在编写一个终端 控制台 应用程序 该应用程序应该包装任意 unicode 文本 终端通常使用等宽 固定宽度 字体 因此要换行文本 只需计算字符数并观察单词是否适合一行并采取相应的操作 问题是 Unicode 表中的全角字符在终端中占用了
  • 在 VS 中运行时如何查看 C# 控制台程序的输出?

    我刚刚编写了一个名为 helloworld 的聪明程序 它是一个 C NET 4 5 控制台应用程序 在扭曲的嵌套逻辑迷宫深处 使用了 Console WriteLine 当我在命令行运行它时 它会运行并且我会看到输出 我可以执行其他命令并
  • 在 .NET MAUI 中实现 TouchTracking

    我一直致力于将我们的应用程序从 Xamarin Forms 迁移到 NET MAUI 我们的应用程序几乎没有绘图功能 用户可以用手指进行绘图 我们用了TouchTrackingXamarin Forms 中的 nuget 包 但与 NET
  • 在 C 中使用枚举而不是 #defines 作为编译时常量是否合理?

    在 C 工作了一段时间后 我将回到 C 开发领域 我已经意识到 在不必要的时候应该避免使用宏 以便让编译器在编译时为您做更多的工作 因此 对于常量值 在 C 中我将使用静态 const 变量或 C 11 枚举类来实现良好的作用域 在 C 中
  • C++ - 多维数组

    处理多维数组时 是否可以为数组分配两种不同的变量类型 例如你有数组int example i j 有可能吗i and j是两种完全不同的变量类型 例如 int 和 string 听起来您正在寻找 std vector
  • 将 Word 转换为 PDF - 禁用“保存”对话框

    我有一个用 C 编写的 Word 到 PDF 转换器 除了一件事之外 它工作得很好 有时 在某些 Word 文件上 后台会出现一条消息保存源文件中的更改 gt 是 否 取消 但我没有对源文件进行任何更改 我只想从 Word 文件创建 PDF
  • 将函数参数类型提取为参数包

    这是一个后续问题 解包 元组以调用匹配的函数指针 https stackoverflow com questions 7858817 unpacking a tuple to call a matching function pointer
  • 使动态创建的链接标签在 Winforms 中可点击

    我正在制作一个程序 允许用户单击由动态链接标签创建的公司名称 在我想知道如何做到这一点之前 我从未在 C 中使用过链接标签 可为特定用户生成的业务数量各不相同 因此每个用户的链接标签数量并不相同 然后我想捕获业务 ID 以进行 Json 调
  • Visual Studio 2015 - Web 项目上缺少共享项目参考选项卡

    我从 MSDN 订阅升级到 Visual Studio 2015 因为我非常兴奋地阅读有关共享项目的信息 当我们想要做的只是重用代码时 不再需要在依赖项中管理 21382 个 nuget 包 所以我构建了一个测试共享项目 其中包含一些代码
  • 为什么空循环使用如此多的处理器时间?

    如果我的代码中有一个空的 while 循环 例如 while true 它将把处理器的使用率提高到大约 25 但是 如果我执行以下操作 while true Sleep 1 它只会使用大约1 那么这是为什么呢 更新 感谢所有精彩的回复 但我

随机推荐

  • Cuda Streams的概述(一)-- Cuda介绍

    最近在做有关Cuda的一个项目 碰到匪夷所思的问题 在异步的时候发现并没有达到预期的效果 程序没有异步起来 然后在网上找了一个Nvida的有关Cuda Streams的一个ppt 然后照着里面的提示 使程序达到了异步的效果 首先 先回顾一下
  • linux VLAN配置(vconfig)

    1 安装vlan vconfig 和加载8021q模块 aptitude install vlan modprobe 8021q 或 yum install vconfig modprobe 8021q lsmod grep i 8021q
  • AndroidStudio编译构建报错 Task ‘wrapper‘ not found in project ‘:xxx‘

    在项目 app 中找不到的任务 包装器 Task wrapper not found in project xxx 的问题 原因 build gradle 文件没有包装器任务 解决步骤 将此代码添加到 build gradle task w
  • PO、VO、DAO、BO、DTO、POJO 能分清吗?

    一 PO persistant object 持久对象 可以看成是与数据库中的表相映射的java对象 使用Hibernate来生成PO是不错的选择 二 VO value object 值对象 通常用于业务层之间的数据传递 和PO一样也是仅仅
  • vue中的h函数与JSX语法

    vue不仅像react一样实现了jsx 而且还借助jsx发挥了javascript动态画的优势 了解学习jsx可以让你更灵活的开发需求 一 h函数 在聊vue中的JSX之前 需要简单介绍一下 h 函数 理解了 h 函数 会更好的理解JSX
  • 华为OD机试 - 士兵过河(Java)

    题目描述 一支N个士兵的军队正在趁夜色逃亡 途中遇到一条湍急的大河 敌军在T的时长后到达河面 没到过对岸的士兵都会被消灭 现在军队只找到了1只小船 这船最多能同时坐上2个士兵 当1个士兵划船过河 用时为 a i 0 lt i lt N 当2
  • mybatis更新和插入语句报错的原因和解决方法

    做一个小项目时 想实现添加和修改用户的功能 发现以下语句程序报错 INSERT INTO hrm user NAME PASS WORD STATUS DESC role createDATE createUSER VALUES 1 2 3
  • WorldEdit 指令大全 & 开发记录

    文章目录 服务端 客户端 运行配置 通用指令 好用刷子 圆球刷 Brush Sphere br sp 地板刷子 Brush Cyl br cyl 树木刷子 tree 服务端 客户端 运行配置 服务端 Spigot 1 14 4 插件版本 W
  • word页面上方有横线选不中删不掉

    依次点击 设计 页面边框 选择无
  • 批处理文件命名(简单有效且粗暴!)

    批处理文件命名 简单有效且粗暴 方法 方法一 方法二 方法 网上的方法层出不穷 但是真正有效的几乎没有 本文罗列外加自己领悟 给出了两个真实且有效的方法 方法一 直接下载粗暴且有效的工具 Totao commander 这是官网的简介 To
  • Apache -poi

    Busy Developers Guide to Features Want to use HSSF and XSSF read and write spreadsheets in a hurry This guide is for you
  • 添加了@LoadBalanced注解,即可实现负载均衡功能,这是什么原理呢?

    SpringCloud01 认识微服务 任何架构都离不开服务的拆分 微服务的拆分和远程调用你会吗 Eureka学习 搭建eureka server 将user service注册到eureka server中 Ribbon负载均衡 上一节中
  • json/xml/schema

    JSON JSON是JavaScript Object Notation的缩写 是一种轻量级的数据交换格式 是理想的接口数据交换语言 官网 https www json org json en html 工作json请求体 json字符串
  • docker cp拷贝文件_Docker - A焕然一新

    Docker的基本组成 镜像 容器 仓库 镜像 image 就像是一个模板 可以通过这个模板来创建容器服务 通过镜像可以创建多个容器 最终服务运行或者项目运行就在容器中 容器 container Docker利用容器技术 独立运行一个或者一
  • [附源码]java毕业设计网上博物馆设计

    项目运行 环境配置 Jdk1 8 Tomcat7 0 Mysql HBuilderX Webstorm也行 Eclispe IntelliJ IDEA Eclispe MyEclispe Sts都支持 项目技术 SSM mybatis Ma
  • 基于wsl2在windows下使用docker应用

    wsl2重启 管理员权限打开powershell 然后执行下面命令 powershell切换为管理员权限 Start Process powershell Verb runAs 关闭服务 net stop LxssManager 重启服务
  • 基于数据库版本的分布式定时任务调度中心

    github调度中心源码地址 https github com yomea timer task scheduler github业务端源码地址 https github com yomea task scheduler starter g
  • 'sqlplus' 不是内部或外部命令,也不是可运行的程序

    在DOS下sqlplus exp imp命令提示 不是内部或外部命令 也不是可运行的程序或批处理文件 首先 确认oracle安装路径下的根目录 oracle home bin目录下的sqlplus exe imp exe exp exe等可
  • GD32F103RC的ADC采样值偏大

    在最近的一个项目中 使用了GD32做控制器 初期调试ADC很顺利 但后期将代码组合后发现ADC的采样值都偏大了 经过反复调试 发现和一个引脚PB0有关 只要将PB0初始化或者外部有电路将电平拉低 PC0 PC1 PC2的ADC采样值就会变大
  • C++之shared_from_this用法以及类自引用this指针陷阱

    C 系列文章目录 文章目录 C 系列文章目录 前言 一 为什么需要enable shared from this 二 enable shared from this用法 总结 前言 shared ptr实现原理 shared ptr 从 P