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.mzph.cn/news/66679.shtml

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

相关文章

没有使用sniffer dongle在windows抓包蓝牙方法分享

网上很多文章都是介绍买一个sniffer dongle来抓蓝牙数据,嫌麻烦又费钱,目前找到一个好方法,不需要sniffer就可以抓蓝牙数据过程,现分享如下: (1)在我资源附件找到相关安装包或者查看如下链接 https://learn.microsoft.com/zh-cn/windows-hardware/drivers/bluetooth/testing-bt…

【Python】批量下载页面资源

【背景】 有一些非常不错的资源网站,比如一些MP3资源网站。资源很丰富,但是每一个资源都不大,一个一个下载费时费力,想用Python快速实现可复用的批量下载程序。 【思路】 获得包含资源链接的静态页面,用beautifulsoup分析页面,获得所有MP3资源的实际地址,然后下载。…

安卓逆向 - Frida反调试绕过

本文仅供学习交流&#xff0c;只提供关键思路不会给出完整代码&#xff0c;严禁用于非法用途&#xff0c;谢绝转载&#xff0c;若有侵权请联系我删除&#xff01; 本文案例 app&#xff1a;5Lqs5LicYXBwMTEuMy4y 一、引言&#xff1a; Frida是非常优秀的一款 Hook框架&#…

uni-app:允许字符间能自动换行(英文字符、数字等)

<template><view class"container"><!-- 这里是你的文本内容 -->{{ multilineText }}</view> </template><style> .container {word-break: break-all; } </style>例如&#xff1a; <template><view class"…

jQuery成功之路——jQuery的DOM操作简单易懂

jQuery的DOM操作 1.jQuery操作内容 jQuery操作内容 1. text() 获取或修改文本内容 类似于 dom.innerText 2. html() 获取或修改html内容 类似 dom.innerHTML 注意: 1. text() 是获取设置所有 2. html() 是获取第一个,设置所有 <!DOCTYPE html> <html lang"zh…

Android学习之路(13) Handler详解

1. 简介 Handler是一套 Android 消息传递机制,主要用于线程间通信。 用最简单的话描述&#xff1a; handler其实就是主线程在起了一个子线程&#xff0c;子线程运行并生成Message&#xff0c;Looper获取message并传递给Handler&#xff0c;Handler逐个获取子线程中的Message.…

Rstudio开不开了怎么办?R is taking longer to start than usual

Rstudio Server 启动时卡死 在使用 linux 服务器版 RstudioServer 的过程中&#xff0c;发现出现了一个问题&#xff0c;导致没有办法正常载入工作页面&#xff0c;网页提示信息是“R is taking longer to start than usual”&#xff0c;直接翻译过来就是“这次启动 R 会比平…

淘宝/天猫获得淘宝商品详情 API 接口文档

item_get-获得淘宝商品详情 API测试工具 注册开通 taobao.item_get 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&#xff09;[item_sear…

cms系统稳定性压力测试出现TPS抖动和毛刺的性能bug【杭州多测师_王sir】

一、并发线程数100&#xff0c;分10个阶梯&#xff0c;60秒加载时间&#xff0c;运行1小时进行压测&#xff0c;到10分钟就出现如下 二、通过jstat -gcutil 16689 1000进行监控

机器学习——决策树与随机森林

机器学习——决策树与随机森林 文章目录 前言一、决策树1.1. 原理1.2. 代码实现1.3. 网格搜索1.4. 可视化决策树 二、随机森林算法2.1. 原理2.2. 代码实现 三、补充&#xff08;过拟合与欠拟合&#xff09;总结 前言 决策树和随机森林都是常见的机器学习算法&#xff0c;用于分…

牛客网刷题

牛客网刷题-C&C 2023年9月3日15:58:392023年9月3日16:37:01 2023年9月3日15:58:39 2023年9月3日16:37:01 整型常量和实型常量的区别

华为静态路由配置实验(超详细讲解+详细命令行)

系列文章目录 华为数通学习&#xff08;7&#xff09; 前言 一&#xff0c;静态路由配置 二&#xff0c;网络地址配置 AR1的配置&#xff1a; AR2的配置&#xff1a; AR3的配置&#xff1a; 三&#xff0c;测试是否连通 AR1的配置: 讲解&#xff1a; AR2的配置&#…

CentOS 7.6源码安装gdb 12.1

参考文章&#xff1a;《GDB调试-从安装到使用》 gdb --version看一下当前gdb的版本&#xff0c;可以看到是7.6.1-120.el7。 https://www.sourceware.org/gdb/download/可以下载gdb源码。 sudo nohup wget https://sourceware.org/pub/gdb/releases/gdb-12.1.tar.xz &下…

跨站请求伪造(CSRF)攻击与防御原理

跨站请求伪造&#xff08;CSRF&#xff09; 1.1 CSRF原理 1.1.1 基本概念 跨站请求伪造&#xff08;Cross Site Request Forgery&#xff0c;CSRF&#xff09;是一种攻击&#xff0c;它强制浏览器客户端用户在当前对其进行身份验证后的Web 应用程序上执行非本意操作的攻击&a…

什么是malloxx勒索病毒,服务器中malloxx勒索病毒了怎么办?

Malloxx勒索病毒是一种新型的电脑病毒&#xff0c;它通过加密用户电脑中的重要文件数据来威胁用户&#xff0c;并以此勒索钱财。这种病毒并不是让用户的电脑瘫痪&#xff0c;而是以非常独特的方式进行攻击。在感染了Malloxx勒索病毒后&#xff0c;它会加密用户服务器中的数据&a…

深入探讨Java虚拟机(JVM):执行流程、内存管理和垃圾回收机制

目录 什么是JVM&#xff1f; JVM 执行流程 JVM 运行时数据区 堆&#xff08;线程共享&#xff09; Java虚拟机栈&#xff08;线程私有&#xff09; 什么是线程私有? 程序计数器&#xff08;线程私有&#xff09; 方法区&#xff08;线程共享&#xff09; JDK 1.8 元空…

【LeetCode-面试经典150题-day18】

目录 17.电话号码的字母组合 77.组合 46.全排列 52.N皇后Ⅱ 17.电话号码的字母组合 题意&#xff1a; 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xf…

mysql:[Some non-transactional changed tables couldn‘t be rolled back]不支持事务

1. mysql创建表时默认引擎MyIsam&#xff0c;因此不支持事务的操作&#xff1b; 2. 修改mysql的默认引擎&#xff0c;可以使用show engine命令查看支持的引擎&#xff1a; 【my.conf详情说明】my.cnf配置文件注释详解_xiaolin01999的博客-CSDN博客 3. 原来使用MyIsam创建的表…

Linux系统中驱动面试分享

​ 1、驱动程序分为几类&#xff1f; 字符设备驱动 块设备驱动 网络设备驱动 2、字符设备驱动需要实现的接口通常有哪些 open、close、read、write、ioctl等接口。 3、主设备号与次设备号的作用 主设备号和次设备号是用来标识系统中的设备的&#xff0c;主设备号用来标识…

postgresql并行查询(高级特性)

######################## 并行查询 postgresql和Oracle一样支持并行查询的,比如select、update、delete大事无开启并行功能后,能够利用多核cpu,从而充分发挥硬件性能,提升大事物的处理效率。 pg在9.6的版本之前是不支持的并行查询的,从9.6开始支持并行查询,但是功能非常…