C++智能指针之weak_ptr(保姆级教学)

目录

C++智能指针之weak_ptr

概述

作用

本文涉及的所有程序

使用说明

weak_ptr的常规操作

lock();

use_count();

expired();

reset();

shared_ptr & weak_ptr

尺寸

智能指针结构框架

常见使用问题

shared_ptr多次引用同一数据,会导致两次释放同一内存(只涉及shared_ptr)

shared_ptr循环引用导致内存泄露(涉及shared_ptr和weak_ptr)

shared_ptr指向局部变量的地址,会导致两次释放同一个内存(只涉及shared_ptr)

shared_ptr接收shared_ptr所实例化对象的this指针导致,会导致两次释放同一个内存(只涉及shared_ptr)


C++智能指针之weak_ptr

概述

std::weak_ptr 是一种智能指针,通常不单独使用,只能和 shared_ptr 类型指针搭配使用,可以视为 shared_ptr 指针的一种辅助工具。借助 weak_ptr 类型指针可以获取 shared_ptr 指针的一些状态信息,比如有多少指向相同空间的 shared_ptr 指针、通过expired()判断shared_ptr 指针指向的堆内存是否已经被释放等等,还可以解决shared_ptr 循环引用的问题。

  • weak_ptr:类模板,弱指针(弱引用计数)
  • weak_ptr弱指针,不会控制影响对象的生命周期(不会改变对象的引用计数),shared_ptr释放指向对象时,是不会考虑weak_ptr是否指向该对象
  • weak_ptr不是独立指针,不能单独操作所指向的资源(不配拥有对象),更不能指向一个新的空间;

作用

  • weak_ptr指针一般用来辅助shared_ptr的使用(监视shared_ptr指向对象的生命周期)
  • weak_ptr和shared_ptr之间可以相互转换,shared_ptr可以直接赋值给weak_ptr,但是反过来是行不通的,需要使用lock函数。

本文涉及的所有程序

00_code.cpp

#include <iostream>
#include <memory>using namespace std;class A
{
public:A(){cout << "A" << endl;}A(int num) : m_num(num){cout << "A int" << endl;}A(const A&& other) : m_num(other.m_num){cout << "A move int" << endl;}~A(){cout << "~A" << endl;}public:int m_num;
};int main(int argc, char const* argv[])
{shared_ptr<A> pa(new A(5));weak_ptr<A> wpa = pa;weak_ptr<A> wpa2 = pa;weak_ptr<A> wpa3 = pa;// weak_ptr常用功能auto pb = wpa.lock();if (pb == nullptr){cout << "pb is nullptr" << endl;}// use_count();返回的是shared_ptr的引用计数cout << wpa.use_count() << endl;// expired():判断当前弱指针指向的对象是否被释放if (wpa.expired()){cout << "wpa pointer class is free" << endl;}wpa.reset();return 0;
}

01_code.cpp

#include <iostream>
#include <memory>using namespace std;class Child;class Parent
{
public:Parent(){cout << "Parent" << endl;}~Parent(){cout << "~Parent" << endl;}//shared_ptr<Child> c;weak_ptr<Child> c;
};class Child
{
public:Child(){cout << "Child" << endl;}~Child(){cout << "~Child" << endl;}shared_ptr<Parent> p;
};int main(int argc, char const* argv[])
{shared_ptr<Parent>pp(new Parent());//pp:1shared_ptr<Child>cc(new Child());//cc:1//循环引用pp->c = cc;//cc:1 pp:1cc->p = pp;//pp:2 cc:1cout << pp.use_count()<<endl;cout << cc.use_count() << endl;return 0;
}

02_code.cpp

#include <iostream>
#include <memory>using namespace std;int main(int argc, char const *argv[])
{shared_ptr<int> p(new int(5));weak_ptr<int> wp = p;//shared_ptr/weak_ptr的尺寸大小是裸指针的两倍cout << sizeof(int *) << endl;cout << sizeof(p) << endl;cout << sizeof(wp) << endl;return 0;
}

03_code.cpp

#include <iostream>
#include <memory>using namespace std;class A:public enable_shared_from_this<A>
{
public:A(){cout << "A" << endl;}A(int num) : m_num(num){cout << "A int" << endl;}A(const A &&other) : m_num(other.m_num){cout << "A move int" << endl;}shared_ptr<A> getAddr(){//return shared_ptr<A>(this);return shared_from_this();//返回可共享的this指针}~A(){cout << "~A" << endl;}public:int m_num;
};int main(int argc, char const *argv[])
{// A a;// shared_ptr<A>temp(&a);// shared_ptr<A> temp = a.getAddr();// int num = 5;// shared_ptr<int>p(&num);//shared_ptr<A> pa(new A());A *pa = new A();shared_ptr<A> temp = pa->getAddr();return 0;
}

使用说明

在VS2022中进行调试,执行完第一条语句后,pa的强引用计数加1

执行完第二句的弱指针赋值后,发现多了一个弱引用计数,和强引用计数一样都为1

增加pa.reset()的操作。通过调试可以发现:不关心是否有弱指针指向当前对象,只要指向当前的指针强引用计数为0了,当前对象就会调用析构函数释放空间。

weak_ptr无法指向一个新的空间(只能指向已有的智能指针),它不配拥有一个对象,只能作为一个指向

weak_ptr不可以直接赋值给shared_ptr

weak_ptr的常规操作

lock();

获取弱指针指向的对象对应的共享指针,如果指向的对象释放,那么返回一个nullptr

调用lock函数来获得shared_ptr(如果对象已经被释放,则返回一个空的shared_ptr)

(有些书上叫做将弱指针转换为共享指针)

在VS2022下调试结果如下:

在调用lock前,pa的强引用计数为1

在调用lock后,pa的强引用计数变为2

use_count();

功能:返回有多少个shared_ptr智能指针指向某对象;(引用计数的个数)

用途:主要用于调试

expired();

判断弱指针是否过期(所指向的对象是否被释放true/false)

reset();

将该弱指针设置为空,弱引用计数减1,强引用计数不变

执行wpa.reset前,弱引用计数为3,强引用计数为2

执行wpa.reset后,弱引用计数减1,变为2;强引用计数仍为2

shared_ptr & weak_ptr

尺寸

shared_ptr和weak_ptr一样大,是裸指针的两倍;

智能指针结构框架

从中可以发现智能指针实际上由两个指针组成:一个指针指向数据,一个指针指向控制块

常见使用问题

shared_ptr多次引用同一数据,会导致两次释放同一内存(只涉及shared_ptr)

int* pInt = new int[100];shared_ptr sp1(pInt);// 一些其它代码之后…shared_ptr sp2(pInt);

shared_ptr循环引用导致内存泄露(涉及shared_ptr和weak_ptr)

我们定义了两个类:Parent和Child,两个类没有继承关系;在Parent中定义了一个Child的智能指针,在Child中定义了一个指向Parent类型的智能指针

在main函数中,定义分别定义Parent和Child类型的指针,让它们内部的指针互相指向

这样就产生了循环引用的现象

编译报错,这是由于未前置声明Child类,Parent类中找不到Child

加上前置声明

重新编译运行结果如下:发现两个类只构造了,没有析构释放,导致了内存泄漏

通过VS2022调试可以发现,两个main中的智能指针在循环引用后,引用计数都变成了2。在程序运行结束时,main中的两个智能指针释放了之后,引用计数减1后变为1,大于0;而两个在类中定义的智能指针,由于它们属于类中的属性,它们必须在析构函数被调用了才能释放,而程序结束引用计数不为0,也就无法调用析构函数。因此这样就导致了内存泄漏。

以图示说明如下:

解决方法:我们将类中的两个指针随便一个改为weak_ptr

如图,我修改的是Parent中的指针,运行发现两个对象空间可以被正常释放

分析:由于Parent类中的是weak_ptr,因此执行完p->c = cc;cc->p = pp;后,cc的强引用计数不变,仍为1,pp的强引用计数为2;当main中的return 0;执行完之后,局部变量释放,pp引用计数变成1,cc引用计数变为0,从而会调用Child的析构函数,将Child类中的shared_ptrp释放,因此pp的引用计数也变为0,最终调用Parent的析构函数,将全部空间释放掉。

shared_ptr指向局部变量的地址,会导致两次释放同一个内存(只涉及shared_ptr)

我们在类中定义了一个函数,用于返回当前对象的地址,其中this指针使用shared_ptr进行包装。

在main中实例化一个对象,并用一个智能指针来获取对象地址。

发现报错:段错误,局部对象被释放了两次

这是由于a是局部对象,它在程序运行结束的时候会自己调用析构函数进行释放,而temp是指向这个局部变量的智能指针,它在程序结束的时候会再次释放局部变量,因此导致了空间被释放两次,产生了段错误。与下图情况一模一样

同样使用智能指针接收对象的this指针也不行

解决方法:

通过裸指针申请空间的方法,实例化对象,然后再用智能指针接收对象返回值

shared_ptr接收shared_ptr所实例化对象的this指针导致,会导致两次释放同一个内存(只涉及shared_ptr)

继续以上面的class A为例,通过智能指针实例化从堆区new出来的对象,通过智能指针接收对象的this指针,也会导致空间被释放两次

解决方法:

针对通过智能指针实例化从堆区new出来的对象,通过智能指针接收对象的地址。而对于任何局部变量此方法无效(我们也可以使用上面的方法,直接使用裸指针从堆区实例化对象)

我们需要继承一个模板类enable_shared_from_this,并将要返回的this指针改为shared_from_this(),此方法可以返回可共享的this指针

运行结果:

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

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

相关文章

ChatGPT在医疗领域可应用于改善与患者的沟通

注意&#xff1a;本信息仅供参考&#xff0c;发布该内容旨在传递更多信息的目的&#xff0c;并不意味着赞同其观点或证实其说法。 自从ChatGPT在2022年末对公众开放以来&#xff0c;OpenAI的这款生成式AI聊天机器人在医疗领域展示出了巨大潜力。它已经通过了美国医学执照考试&a…

Paddle训练COCO-stuff数据集学习记录

COCO-stuff数据集 COCO-Stuff数据集对COCO数据集中全部164K图片做了像素级的标注。 80 thing classes, 91 stuff classes and 1 class ‘unlabeled’ 数据集下载 wget --directory-prefixdownloads http://images.cocodataset.org/zips/train2017.zip wget --directory-prefi…

“媒体+”时代正当时,ATEN以前瞻解决方案助推媒体融合纵深发展

自媒体融合概念提出以来,传统媒体与新媒体融合速度加快,两者的相互结合与优势互补为广电行业发展提供了新的契机,更加多元化、个性化、强互动的“媒体”传播格局已逐渐形成。 “媒体”理念的创建,对于广电行业而言无疑是一种积极的改革创新之举,然而“媒体”的发展也呈现出泛媒…

Python实现自动关键词提取

随着互联网的发展&#xff0c;越来越多的人喜欢在网络上阅读小说。本文将通过详细示例&#xff0c;向您介绍如何使用Python编写爬虫程序来获取网络小说&#xff0c;并利用自然语言处理技术实现自动文摘和关键词提取功能。 1. 网络小说数据抓取 首先&#xff0c;请确保已安装必…

Show that f(z)=1/z is analytic or not

See https://brainly.in/question/21838444

PaddleNLP使用Vicuna

LLaMA 模型 LLaMa 是一个大型语言模型&#xff0c;由 Meta 开源。它的全称是 Large Language Model Meta AI&#xff0c;参数量从 70 亿到 650 亿不等。例如&#xff0c;130 亿参数的 LLaMA 模型在大多数基准上可以胜过参数量达 1750 亿的 GPT-3&#xff0c;而且可以在单块 V1…

HTTP协议初识·中篇

加上目录&#xff0c;会出现导向不正确的情况&#xff0c;可能是bug&#xff0c;目录一长就容易出错&#xff1f; 本篇主要讲解了&#xff1a; 网页分离(网页代码和.c文件分离) html链接跳转 网页添加图片 确认并返回资源类型 填写正文长度属性 添加表单 临时重定向 补充知识&a…

Jdk8 动态编译 Java 源码为 Class 文件(三)

Jdk8 动态编译 Java 源码为 Class 文件 一.JDK版本二.工程介绍1.依赖2.启动类3.配置类&#xff08;用于测试依赖注入&#xff09;4.工具类1.Java 源码文件读取类2.SpringBoot 容器实例管理类 5.测试类1.抽象类2.接口类3.默认抽象实现4.默认接口实现 6.接口类1.测试接口2.类重载…

SpringWeb(SpringMVC)

目录 SpringWeb介绍 搭建 SpringWeb SpringWeb介绍 Spring Web是一个基于 Servlet API 构建的原始 web 框架&#xff0c;用于构建基于MVC模式的Web应用程序。在 web 层框架历经 Strust1&#xff0c;WebWork&#xff0c;Strust2 等诸多产品的历代更选 之后&#xff0c;目前业界普…

QT DAY4

一、使用鼠标时间完成组件的移动 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include<QDebug> #include<QMouseEvent>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widg…

LLMs:OpenAI官方重磅更新——新增GPT-3.5Turbo调和API更新功能

LLMs&#xff1a;OpenAI官方重磅更新——新增GPT-3.5Turbo调和API更新功能 导读&#xff1a;2023年8月22日&#xff0c;OpenAI官方发布&#xff0c;开发者现在可以使用自己的数据来定制适用于其用例的GPT-3.5 Turbo模型。GPT-3.5 Turbo的微调现在已经可用&#xff0c;GPT-4的微…

【算法与数据结构】106、LeetCode从中序与后序遍历序列构造二叉树

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;首先我们要知道后序遍历数组的最后一个元素必然是根节点&#xff0c;然后根据根节点在中序遍历数组中的…

【LeetCode-中等题】994. 腐烂的橘子

文章目录 题目方法一&#xff1a;bfs层序遍历 题目 该题值推荐用bfs&#xff0c;因为是一层一层的感染&#xff0c;而不是一条线走到底的那种&#xff0c;所以深度优先搜索不适合 方法一&#xff1a;bfs层序遍历 广度优先搜索&#xff0c;就是从起点出发&#xff0c;每次都尝…

Android GB28181客户端开发(1):GB28181协议简介

Android GB28181客户端开发(1):GB28181协议简介 公共安全视频监控联网系统信息传输、交换、控制技术要求(2016版) 源码请翻到文章结尾 介绍GB28181协议 GB28181协议是一种基于IP网络的远程视频监控系统,它定义了设备之间的通信协议和数据格式。GB28181协议的主要特点是支…

【Rust】001-基础语法:变量声明及数据类型

【Rust】001-基础语法&#xff1a;变量声明及数据类型 文章目录 【Rust】001-基础语法&#xff1a;变量声明及数据类型一、概述1、学习起源2、依托课程 二、入门程序1、Hello World2、交互程序代码演示执行结果 3、继续上难度&#xff1a;访问链接并打印响应依赖代码执行命令 三…

Collections和CollectionUtils集合操作

0.引入依赖 <dependency><groupId>org.apache.commons</groupId><artifactId>commons-collections4</artifactId><version>4.4</version> </dependency> 一.Collections用法&#xff1a; 01、排序操作 reverse(List list)…

【摆烂之小左】Maven配置IDEA教程

Maven是什么 Maven项目对象模型(POM)&#xff0c;可以通过一小段描述信息来管理项目的构建&#xff0c;报告和文档的项目管理工具软件。 Maven 除了以程序构建能力为特色之外&#xff0c;还提供高级项目管理工具。由于 Maven 的缺省构建规则有较高的可重用性&#xff0c;所以常…

数学建模:模糊综合评价分析

&#x1f506; 文章首发于我的个人博客&#xff1a;欢迎大佬们来逛逛 数学建模&#xff1a;模糊综合评价分析 文章目录 数学建模&#xff1a;模糊综合评价分析综合评价分析常用评价方法一级模糊综合评价综合代码 多级模糊综合评价总结 综合评价分析 构成综合评价类问题的五个…

go语言基础操作--二

a : 10str : "mike"//匿名函数&#xff0c;没有函数名字 形成一个闭包,函数定义&#xff0c;还没有调用f1 : func() { //:自动推到类型fmt.Println("a ", a)fmt.Println("str ", str)}f1()//给一个函数类型起别名 这个写法不推荐type FuncType …

Flutter状态管理 — 探索Flutter中的状态

前言 随着响应式编程的理念&Flutter被大众所了解以来&#xff0c;状态管理一直是一个引人深思的话题。如果想要学习好Flutter这样的响应式的编程框架就一定是离不开状态管理的。我遇到过很多没有了解过响应式编程框架的&#xff0c;或者从事后端开发&#xff0c;自己想用F…