c++11特新:弱引用智能指针

弱引用智能指针std::weak_ptr可以看做是shared_ptr的助手,它不管理shared_ptr内部的指针。std::weak_ptr没有重载操作符*->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视shared_ptr中管理的资源是否存在。

1. 初始化

// 默认构造函数
constexpr weak_ptr() noexcept;
// 拷贝构造
weak_ptr (const weak_ptr& x) noexcept;
template <class U> weak_ptr (const weak_ptr<U>& x) noexcept;
// 通过shared_ptr对象构造
template <class U> weak_ptr (const shared_ptr<U>& x) noexcept;

在C++11中,weak_ptr的初始化可以通过以上提供的构造函数来完成初始化,具体使用方法如下: 

#include <iostream>
#include <memory>
using namespace std;

int main() 
{
    shared_ptr<int> sp(new int);

    weak_ptr<int> wp1;
    weak_ptr<int> wp2(wp1);
    weak_ptr<int> wp3(sp);
    weak_ptr<int> wp4;
    wp4 = sp;
    weak_ptr<int> wp5;
    wp5 = wp3;
    
    return 0;
}

1. weak_ptr<int> wp1;构造了一个空weak_ptr对象
2. weak_ptr<int> wp2(wp1);通过一个空weak_ptr对象构造了另一个空weak_ptr对象
3. weak_ptr<int> wp3(sp);通过一个shared_ptr对象构造了一个可用的weak_ptr实例对象
4. wp4 = sp;通过一个shared_ptr对象构造了一个可用的weak_ptr实例对象(这是一个隐式类型转换)
5. wp5 = wp3;通过一个weak_ptr对象构造了一个可用的weak_ptr实例对象

2. 其他常用方法

 2.1 use_count():

通过调用std::weak_ptr类提供的use_count()方法可以获得当前所观测资源的引用计数,函数原型如下:

// 函数返回所监测的资源的引用计数
long int use_count() const noexcept;

修改一下上面的测试程序,添加打印资源引用计数的代码:

#include <iostream>
#include <memory>
using namespace std;

int main() 
{
    shared_ptr<int> sp(new int);

    weak_ptr<int> wp1;
    weak_ptr<int> wp2(wp1);
    weak_ptr<int> wp3(sp);
    weak_ptr<int> wp4;
    wp4 = sp;
    weak_ptr<int> wp5;
    wp5 = wp3;

    cout << "use_count: " << endl;
    cout << "wp1: " << wp1.use_count() << endl;
    cout << "wp2: " << wp2.use_count() << endl;
    cout << "wp3: " << wp3.use_count() << endl;
    cout << "wp4: " << wp4.use_count() << endl;
    cout << "wp5: " << wp5.use_count() << endl;
    return 0;
}

测试程序输出的结果为:

use_count:
wp1: 0
wp2: 0
wp3: 1
wp4: 1
wp5: 1

通过打印的结果可以知道,虽然弱引用智能指针wp3、wp4、wp5监测的资源是同一个,但是它的引用计数并没有发生任何的变化,也进一步证明了weak_ptr只是监测资源,并不管理资源。 

 2.2 expired():

通过调用std::weak_ptr类提供的expired()方法来判断观测的资源是否已经被释放,函数原型如下:

// 返回true表示资源已经被释放, 返回false表示资源没有被释放
bool expired() const noexcept;

函数的使用方法如下:

#include <iostream>
#include <memory>
using namespace std;

int main() 
{
    shared_ptr<int> shared(new int(10));
    weak_ptr<int> weak(shared);
    cout << "1. weak " << (weak.expired() ? "is" : "is not") << " expired" << endl;

    shared.reset();
    cout << "2. weak " << (weak.expired() ? "is" : "is not") << " expired" << endl;

    return 0;
}

测试代码输出的结果:

1. weak is not expired
2. weak is expired

weak_ptr监测的就是shared_ptr管理的资源,当共享智能指针调用shared.reset();之后管理的资源被释放,因此weak.expired()函数的结果返回true,表示监测的资源已经不存在了。

 2.3  lock():

 通过调用std::weak_ptr类提供的lock()方法来获取管理所监测资源的shared_ptr对象,函数原型如下:

shared_ptr<element_type> lock() const noexcept;

函数的使用方法如下:

#include <iostream>
#include <memory>
using namespace std;

int main()
{
    shared_ptr<int> sp1, sp2;
    weak_ptr<int> wp;

    sp1 = std::make_shared<int>(520);
    wp = sp1;
    sp2 = wp.lock();
    cout << "use_count: " << wp.use_count() << endl;

    sp1.reset();
    cout << "use_count: " << wp.use_count() << endl;

    sp1 = wp.lock();
    cout << "use_count: " << wp.use_count() << endl;

    cout << "*sp1: " << *sp1 << endl;
    cout << "*sp2: " << *sp2 << endl;

    return 0;
}

测试代码输出的结果为:

use_count: 2
use_count: 1
use_count: 2
*sp1: 520
*sp2: 520

1. sp2 = wp.lock();通过调用lock()方法得到一个用于管理weak_ptr对象所监测的资源的共享智能指针对象,使用这个对象初始化sp2,此时所监测资源的引用计数为2
2. sp1.reset();共享智能指针sp1被重置,weak_ptr对象所监测的资源的引用计数减1
3. sp1 = wp.lock();sp1重新被初始化,并且管理的还是weak_ptr对象所监测的资源,因此引用计数加1
4. 共享智能指针对象sp1sp2管理的是同一块内存,因此最终打印的内存中的结果是相同的,都是520

 2.4 reset():

通过调用std::weak_ptr类提供的reset()方法来清空对象,使其不监测任何资源,函数原型如下:

void reset() noexcept;

函数的使用是非常简单的,示例代码如下:

#include <iostream>
#include <memory>
using namespace std;

int main() 
{
    shared_ptr<int> sp(new int(10));
    weak_ptr<int> wp(sp);
    cout << "1. wp " << (wp.expired() ? "is" : "is not") << " expired" << endl;

    wp.reset();
    cout << "2. wp " << (wp.expired() ? "is" : "is not") << " expired" << endl;

    return 0;
}

测试代码输出的结果为:

1. wp is not expired
2. wp is expired

weak_ptr对象sp被重置之后wp.reset();变成了空对象,不再监测任何资源,因此wp.expired()返回true 

  3. 返回管理this的shared_ptr

 如果在一个类中编写了一个函数,通过这个得到管理当前对象的共享智能指针,我们可能会写出如下代码:

#include<iostream>
#include<memory>
using namespace std;

/*
	shared_ptr使用的注意事项:
	1. 不能使用一个原始地址初始化多个共享指针
	2. 函数不能返回管理了this的共享智能指针对象
	3. 共享智能指针不能循环引用
*/

struct Test
{
	shared_ptr<Test> getSharedPtr()
	{
		return shared_ptr<Test>(this);
	}

	~Test()
	{
		cout << "class Test is distruct..." << endl;
	}

};

int main()
{
	//Test* t = new Test;
	//shared_ptr<Test> ptr1(t);
	 因为两个智能指针对象互相不知道对方存在,各自引用计数肯定都是1,
	 各自释放各自的,所以这种方式是错误的
	shared_ptr<Test> ptr2(t);// error 注意事项1
	//shared_ptr<Test> ptr2 = ptr1;
	
	// sp1和sp2管理的是同一块内存,但是根据注意事项1所以该操作是不允许的
	// 在析构的时候这块内存就会被析构两次
	shared_ptr<Test> sp1(new Test);
	cout << "use_count:" << sp1.use_count() << endl;
	shared_ptr<Test> sp2 = sp1->getSharedPtr();// error
	cout << "use_count:" << sp1.use_count() << endl;
}

执行上面的测试代码,运行中会出现异常,在终端还是能看到对应的日志输出:

use_count: 1
use_count: 1
class Test is disstruct ...
class Test is disstruct ...

通过输出的结果可以看到一个对象被析构了两次,其原因是这样的:在这个例子中使用同一个指针this构造了两个智能指针对象sp1sp2,这二者之间是没有任何关系的,因为sp2并不是通过sp1初始化得到的实例对象。在离开作用域之后this将被构造的两个智能指针各自析构,导致重复析构的错误。

这个问题可以通过weak_ptr来解决,通过wek_ptr返回管理this资源的共享智能指针对象shared_ptr。C++11中为我们提供了一个模板类叫做std::enable_shared_from_this<T>,这个类中有一个方法叫做shared_from_this(),通过这个方法可以返回一个共享智能指针,在函数的内部就是使用weak_ptr来监测this对象,并通过调用weak_ptrlock()方法返回一个shared_ptr对象。

修改之后的代码为:

#include<iostream>
#include<memory>
using namespace std;

/*
	shared_ptr使用的注意事项:
	1. 不能使用一个原始地址初始化多个共享指针
	2. 函数不能返回管理了this的共享智能指针对象
	3. 共享智能指针不能循环引用
*/

struct Test : public enable_shared_from_this<Test>
{
	shared_ptr<Test> getSharedPtr()
	{
		return shared_from_this();
	}
	~Test()
	{
		cout << "class Test is disstruct ..." << endl;
	}
};

int main()
{
	//Test* t = new Test;
	//shared_ptr<Test> ptr1(t);
	 因为两个智能指针对象互相不知道对方存在,各自引用计数肯定都是1,
	 各自释放各自的,所以这种方式是错误的
	shared_ptr<Test> ptr2(t);// error 注意事项1
	//shared_ptr<Test> ptr2 = ptr1;
	
	// sp1和sp2管理的是同一块内存,但是根据注意事项1所以该操作是不允许的
	// 在析构的时候这块内存就会被析构两次
	shared_ptr<Test> sp1(new Test);
	cout << "use_count:" << sp1.use_count() << endl;
	shared_ptr<Test> sp2 = sp1->getSharedPtr();
	cout << "use_count:" << sp1.use_count() << endl;
}

测试代码输出的结果为:

use_count: 1
use_count: 2
class Test is disstruct ...

这样就对了,引用计数为2,且只析构了一次。 

最后需要强调一个细节:在调用enable_shared_from_this类的shared_from_this()方法之前,必须要先初始化函数内部weak_ptr对象,否则该函数无法返回一个有效的shared_ptr对象(具体处理方法可以参考上面的示例代码)。

过程:初始化发生在shared_ptr<Test> sp1(new Test);这条语句中,通过new出一块内存,然后将地址给共享智能指针对象sp1。又因为Test类继承了enable_shared_from_this这个类,所以enable类中的weak_ptr被初始化了,相当于enable类中的weak_ptr对象指向了sp1。然后就可以检测sp1管理的这块内存了。然后调用getSharedPtr()就会返回弱引用智能指针对象检测的内存对应的共享智能指针对象。

 4. 解决循环引用问题

 智能指针如果循环引用会导致内存泄露,比如下面的例子:

#include <iostream>
#include <memory>
using namespace std;

struct TA;
struct TB;

struct TA
{
    shared_ptr<TB> bptr;
    ~TA()
    {
        cout << "class TA is disstruct ..." << endl;
    }
};

struct TB
{
    shared_ptr<TA> aptr;
    ~TB()
    {
        cout << "class TB is disstruct ..." << endl;
    }
};

void testPtr()
{
    shared_ptr<TA> ap(new TA);
    shared_ptr<TB> bp(new TB);
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;

    ap->bptr = bp;
    bp->aptr = ap;
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;
}

int main()
{
    testPtr();
    return 0;
}

测试程序输出的结果如下:

TA object use_count: 1
TB object use_count: 1
TA object use_count: 2
TB object use_count: 2

 

在测试程序中,共享智能指针apbpTATB实例对象的引用计数变为2,在共享智能指针离开作用域之后引用计数只能减为1,这种情况下不会去删除智能指针管理的内存,导致类TATB的实例对象不能被析构,最终造成内存泄露。通过使用weak_ptr可以解决这个问题,只要将类TA或者TB的任意一个成员改为weak_ptr,修改之后的代码如下:        

#include <iostream>
#include <memory>
using namespace std;

struct TA;
struct TB;

struct TA
{
    weak_ptr<TB> bptr;
    ~TA()
    {
        cout << "class TA is disstruct ..." << endl;
    }
};

struct TB
{
    shared_ptr<TA> aptr;
    ~TB()
    {
        cout << "class TB is disstruct ..." << endl;
    }
};

void testPtr()
{
    shared_ptr<TA> ap(new TA);
    shared_ptr<TB> bp(new TB);
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;

    ap->bptr = bp;
    bp->aptr = ap;
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;
}

int main()
{
    testPtr();
    return 0;
}

测试程序输出的结果如下:

TA object use_count: 1
TB object use_count: 1
TA object use_count: 2
TB object use_count: 1
class TB is disstruct ...
class TA is disstruct ...

 

通过输出的结果可以看到类TA或者TB的对象被成功析构了。

上面程序中,在对类TA成员赋值时ap->bptr = bp;由于bptrweak_ptr类型,这个赋值操作并不会增加引用计数,所以bp的引用计数仍然为1,在离开作用域之后bp的引用计数减为0,类TB的实例对象被析构。

在类TB的实例对象被析构的时候,内部的aptr也被析构,其对TA对象的管理解除,内存的引用计数减为1,当共享智能指针ap离开作用域之后,对TA对象的管理也解除了,内存的引用计数减为0,类TA的实例对象被析构。

 本文章参考:弱引用智能指针 | 爱编程的大丙 (subingwen.cn)

 

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DMR与DPMR以及DMR的分层

数字移动无线电 (DMR) 和数字专用移动无线电 (dPMR) 是数字对讲机中使用的流行通信技术。 与传统模拟无线电相比&#xff0c;这两种技术都提供了改进的音频质量、增强的安全功能和增加的网络容量。 但是&#xff0c;DMR 和 dPMR 无线电之间使用的技术存在重大差异&#xff…

【三维生成与重建】ZeroRF:Zero Pretraining的快速稀疏视图360°重建

系列文章目录 题目&#xff1a;ZeroRF: Fast Sparse View 360◦ Reconstruction with Zero Pretraining 任务&#xff1a;稀疏重建&#xff1b;拓展&#xff1a;Image to 3D、文本到3D 作者&#xff1a;Ruoxi Shi* Xinyue Wei* Cheng Wang Hao Su &#xff0c;来自UC San Dieg…

Redis 6 性能大揭秘:如何优化缓存命中率?

Redis 6的性能优化&#xff0c;特别是关于如何优化缓存命中率。 这篇文章会包含10个代码示例&#xff0c;帮助深入理解和应用相关的技巧 1、 监控缓存命中率 在优化之前&#xff0c;首先要了解当前的缓存命中率。Redis提供了INFO命令来查看性能指标&#xff0c;包括命中率。…

51单片机模数转换ADC原理与代码一

51单片机模数转换ADC原理与代码一 1.概述 这篇文章是模数转换的入门文章&#xff0c;这篇文章主要介绍模数的概念、原理、核心指标、专业术语&#xff0c;以及一个模数转换的实例代码实现检测电位器的数值变化。 2.ADC介绍 2.1.ADC概念 ADC(Analog-to-Digital Converter)是…

TrustZone之安全启动与引导失败处理

一、引导和信任链 引导是任何TrustZone系统的关键部分。只有在引导流程中之前运行的所有软件组件都是可信的情况下,才能信任某个软件组件。这通常被称为信任链。下图显示了一个简化的信任链: 在我们的示例中,首先运行的代码是boot ROM。我们必须隐式信任boot ROM,因…

「完美世界」石昊调戏清漪,告白欲以身相许,渡劫神莲淬炼肉身

Hello,小伙伴们&#xff0c;我是拾荒君。 《完美世界》第142集已经更新。石昊在齐道临赠予的令牌庇护下&#xff0c;成功潜入仙池&#xff0c;借助着他的重瞳&#xff0c;他发现那神秘的渡劫神莲正位于这仙池之中。然而&#xff0c;渡劫神莲的位置上空雷云翻滚&#xff0c;宛如…

【JavaWeb学习笔记】14 - 三大组件其二 Listener Filter

API文档JAVA_EE_api_中英文对照版 Listener 一、监听器Listener 1. Listener监听器它是JavaWeb的三大组件之一。 JavaWeb的三大组件分别是: Servlet程序、Listener监听器、Filter过滤器 2. Listener是JavaEE的规范&#xff0c;就是接口 3.监听器的作用是&#xff0c;监听某…

uniapp纯CSS实现圆形进度条组件

uniapp纯CSS实现圆形进度条组件。圆形进度条组件组合做一个步骤进度组件是非常常见。 纯 CSS 实现圆形进度条组件有以下几个好处&#xff1a; 轻量级&#xff1a;由于纯 CSS 实现&#xff0c;无需额外的 JavaScript 或图像资源&#xff0c;所以组件的文件大小相对较小&#xf…

HTML5文档

目录 HTML5文档结构1.HTML5页面结构2.HTML5新增结构元素 HTML5新增页面元素1.hgroup标记2.figure标记与figcaption标记3.mark标记与time标记4.details标记与summary标记5.progress标记与meter标记6.input标记与datalist标记 HTML5文档结构 HTML5文档结构同样是由头部和主体两部…

Postman报:400 Bad Request

● 使用Postman发送Post请求报400&#xff0c;入参为JSON&#xff1b; 二、分析 1、Postman请求并没有请求到后台Api&#xff08;由于语法错误&#xff0c;服务器无法理解请求&#xff09;&#xff1b; 2、入参出错范围&#xff1a;cookie、header、body、form-data、x-www-f…

3.[BUU]warmup_csaw_20161

1.checksec 检查文件类型 ELF-64-little &#xff0c;无其他限权&#xff0c;直接用ida检查代码。 2.IDA进行反编译&#xff0c;进行代码审计 查看各个名称的内容&#xff1a; 了解基本攻击思路&#xff1a; 攻击思路&#xff1a;gets输入垃圾数据覆盖v5内容&#xff0c;再将s…

FPGA-Xilinx ZYNQ PS端实现SD卡文件数据读取-完整代码

FPGA-Xilinx ZYNQ PS端实现SD卡文件数据读取 本章节记录Xilinx ZYNQ PS端实现SD卡txt文件的数据读取。 踩坑记录&#xff0c;本章节主要内容参考原子哥 板子&#xff1a;xilinx zynq 7010 文章目录 FPGA-Xilinx ZYNQ PS端实现SD卡文件数据读取一、开发板引脚配置二、PS端导入F…

嵌入式科普(5)ARM GNU Toolchain相关概念和逻辑

一、目的/概述 二、资料来源 三、逻辑和包含关系 四、Arm GNU Toolchain最常用的命令 嵌入式科普(5)ARM GNU Toolchain相关概念和逻辑 一、目的/概述 对比高集成度的IDE(MDK、IAR等)&#xff0c;Linux开发需要自己写Makefile等多种脚本。eclipse、Visual Studio等需要了解预处…

高德地图逆地理编码踩坑日志

本人是一枚Java小白&#xff0c;公司项目中用到根据经纬度反查该地址中文信息的场景&#xff0c;因为一开始调用的经纬度是能反查出区域编码的&#xff0c;以为towncode都是String返回结果&#xff0c;如下图&#xff1a; 没想到当没有名字任何一个城市区域的时候&#xff0c;…

在Linux安装卸载文件

目录 一、Linux系统应用程序 1.典型的应用程序的目录结构 2、常见的软件包封装类型 二、RPM软件包管理 1、RPM是什么&#xff1f; 2、rpm一般命名格式 3、RPM安装包从何而来&#xff1f;如何挂载&#xff1f; 4、挂载的注意事项: 5、目的&#xff1a;提供安装包 6、查…

MyBatis——MyBatis的延迟加载

MyBatis的延迟加载&#xff08;一对多查询案例&#xff09; 1.什么是延迟加载&#xff1f; 开启延迟加载后&#xff0c;在真正使用数据的时候才发起级联查询&#xff0c;不用的时候不查询。 2.pojo User类&#xff1a; package com.wt.pojo;import java.io.Serializable; …

【华为鸿蒙系统学习】- HarmonyOS4.0之App项目开发|自学篇

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 &#x1f4ab;个人格言:"没有罗马,那就自己创造罗马~" 目录 创建鸿蒙第一个App项目 项目创建 工程目录区 预览区 运行Hello World 基本工程目录 ws:工…

SpringBoot+vue实现评论区分页效果

当评论区含大量数据时&#xff0c;一次性查询速度很慢&#xff0c;所以使用分页&#xff0c;实现分页效果。 前端&#xff1a; <h3>评论</h3><div><div style"font-size:14px;padding:10px;" v-for"r in form.remark"><!-- …

【Matlab in VSCode】在VSCode中编辑MATLAB文件

【Matlab in VSCode】在VSCode中编辑MATLAB文件 1.安装插件 插件&#xff1a;在vscode拓展商店下载 MATLABMatlab in VSCode 其他&#xff1a;Windows环境MATLAB2019bpython3.7.9 2.插件配置 MATLAB插件下载后不用配置。 Matlab in VSCode需要进行相应的配置。 Windows…

Ubuntu 常用命令之 ping 命令用法介绍

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 ping命令是一种网络诊断工具&#xff0c;用于测试主机之间网络的连通性。它发送ICMP Echo Request消息到指定的网络主机&#xff0c;并等待接收ICMP Echo Reply。通过这种方式&#xff0c;我们可以知道两台主机之间的网络是否畅通…