c++的智能指针(5) -- weak_ptr

概述

  • 我们在使用shared_ptr会出现以下的问题,会导致内存泄露。

代码1:  类内指针循环指向

#include <iostream>
#include <memory>

class B;

class A {
public:
	A() {
		std::cout << "Construct" << std::endl;
	}
	~A() {
		std::cout << "Destruct" << std::endl;
	}

	void setPtr(std::shared_ptr<B> b) {
		this->p1 = b;
	}
private:
	std::shared_ptr<B> p1;
};

class B {
public:
	B() {
		std::cout << "Construct" << std::endl;
	}
	~B() {
		std::cout << "Destruct" << std::endl;
	}

	void setPtr(std::shared_ptr<A> a) {
		this->p1 = a;
	}
private:
	std::shared_ptr<A> p1;
};

int main(void) {

	{
	std::shared_ptr<A> pa(new A());
	std::shared_ptr<B> pb(new B());

	pa->setPtr(pb);
	pb->setPtr(pa);
    }

	std::cin.get();
}

结果: 

 

问题:

我们代码中,定义了两个类,main中定义了两个指针指向,分别指向两个类的动态空间,从输出结果会发现,我们pa和pb指向的动态空间,在pa和pb释放的时候并没有释放掉。 
这很显然不符合我们使用智能指针的预期。


因为我们使用智能指针,指向动态开辟的空间,它在自己对象释放的时候并没有释放对应的动态空间。--  这样就会导致内存泄露

原因:

原因是什么呢?  
就是我们的A,B类中都存在有指向对方的智能指针,当我们在main中定义指向类A和类B动态空间的智能指针的时候,同时,我们使用setPtr函数,将其内部的智能指针进行了赋值,分别指向了对方。(A内的智能指针指向B的动态空间,B内的智能指针指向A的动态空间)

这时候,对于动态开辟的A类和B类的动态空间而言,都同时存在着两个智能指针管理(一个是main中定义的,一个是类中的成员),引用计数为2。


这时候,当main中定义的智能指针析构,指向两块动态空间的引用计数-1,但是因为之前引用计数为2,-1之后为1,不是0,所以其不会释放动态开辟的空间。


因为main中指向两块空间的指针已经析构,我们已经无法管理这两块空间,但是它们却没有被释放,所以就造成了内存的泄露

 如图:

解决方法:  

方法一:  在类内部将内部指针设置为空 
class A {
public:
	A() {
		std::cout << "Construct" << std::endl;
	}
	~A() {
		std::cout << "Destruct" << std::endl;
	}

	void setPtr(std::shared_ptr<B> b) {
		this->p1 = b;
	}

	void deletePtr() {
		p1 = nullptr;
	}
private:
	std::shared_ptr<B> p1;
};

class B {
public:
	B() {
		std::cout << "Construct" << std::endl;
	}
	~B() {
		std::cout << "Destruct" << std::endl;
	}

	void setPtr(std::shared_ptr<A> a) {
		this->p1 = a;
	}

	void deletePtr() {
		p1 = nullptr;
	}
private:
	std::shared_ptr<A> p1;
};

int main(void) {

	{
		std::shared_ptr<A> pa(new A());
		std::shared_ptr<B> pb(new B());

		pa->setPtr(pb);
		pb->setPtr(pa);

		pa->deletePtr();
		pb->deletePtr();
    }

	std::cin.get();
}

我们在类的内部添加了deletePtr()函数,主要用来将类中的智能指针设置为空,我们只需要在main中的智能指针析构的时候调用对应类中的deletePtr()函数就可以做到释放动态开辟的空间了。

但是,我们使用智能指针的目的是为了让其帮助我们管理动态空间,如果使用这种方式,就失去了其意义,显然这种方式并方便(因为需要我们自己去调用函数,才能成功释放空间)。 

方式二: 使用weak_ptr智能指针 
int main(void) {

	{
		std::shared_ptr<A> pa(new A());
		std::shared_ptr<B> pb(new B());

		pa->setPtr(pb);
		//pb->setPtr(pa);
    }

	std::cin.get();
}

我们对main中的代码稍作修改,就是将pb调用setPtr()函数给注释掉。
也就是,我们不让类B中的智能指针指向类A的动态空间,这样类A的动态空间的引用计数就为1,在指向类A空间的智能指针pa释放的时候,引用计数减为0,这样类A的动态空间就会被释放。

类A的空间释放之后,那其内部的指向类B动态空间的智能指针也被释放,引用计数-1,然后智能指针pb在main中也被释放,这样这块空间的引用计数为0,也就被释放了。

如图:

weak_ptr 

鉴于上面这种思路,c++11有提出了weak_ptr,用来解决shared_ptr这种循环引用的问题

weak_ptr 

  • weak_ptr是一个智能指针,是用于保存shared_ptr的非拥有引用(弱引用),其不能直接访问指向的对象,必须转换成shared_ptr才可以访问。
  • weak_ptr在构建的时候,不能让它单独指向一个空间,必须使用shared_ptr的对象来构造它(也就是其指向shared_ptr指向的空间)。

    当然也可以将weak_ptr对象初始化为空,也可以使用空的weak_ptr来初始化它。

    也可以使用别的weak_ptr对象来构造其对象。
  • weak_ptr允许使用shared_ptr的对象赋值给它,但也只能这样赋值。(NULL或者nullptr也不能复制给它)

std::shared_ptr<int> p = std::make_shared<int>(5);
std::weak_ptr<int> w;
w = p;

w = NULL; // error

代码: 

 

为什么使用weak_ptr不会造成上面所提到的问题
  • weak_ptr指向与shared_ptr相同的空间,并不会增加其引用计数。

    std::shared_ptr<int> p1 = std::make_shared<int>();
    std::cout << "p1的引用计数" << p1.use_count() << std::endl;   // 输出: 1

    std::weak_ptr<int> w1(p1);
    std::cout << "w1的引用计数" << w1.use_count() << std::endl;   // 输出: 1

 会发现尽管w1和p1指向同一块空间,引用计数不变。

  • weak_ptr是用来辅助shared_ptr使用的,即使其指向一片空间,也仅仅是知道这块空间在哪个位置,不能访问空间中的数据。



我们使用w1访问其指向空间的数据,会发现出错了。

weak_ptr转换成shared_ptr (lock()函数)

我们要想使用weak_ptr对象访问数据等,必须将其转化为shared_ptr.

c++提供了lock()函数,可以帮助我们将weak_ptr转化成shared_ptr。 


lock()函数返回一个shared_ptr对象。如下,我们使用w1调用lock()函数,可以定义一个shared_ptr的对象来接收其返回值。

std::shared_ptr<int> p1 = std::make_shared<int>(5);
std::cout << *p1 << std::endl;  

std::weak_ptr<int> w1(p1);
std::shared_ptr<int> p2 = w1.lock();
std::cout << *p2 << std::endl;    // 这样就可以使用转换的共享指针访问数据。

代码2: 使用weak_ptr解决上面的问题 

#include <iostream>
#include <memory>

class B;

class A {
public:
	A() {
		std::cout << "Construct" << std::endl;
	}
	~A() {
		std::cout << "Destruct" << std::endl;
	}

	void setPtr(std::shared_ptr<B> b) {
		this->p1 = b;
	}
private:
	std::shared_ptr<B> p1;
};

class B {
public:
	B() {
		std::cout << "Construct" << std::endl;
	}
	~B() {
		std::cout << "Destruct" << std::endl;
	}

	void setPtr(std::shared_ptr<A> a) {
		this->p1 = a;
	}

private:
	std::weak_ptr<A> p1;
};

int main(void) {
	{
		std::shared_ptr<A> pa(new A());
		std::shared_ptr<B> pb(new B());

		pa->setPtr(pb);
		pb->setPtr(pa);
	}

	std::cin.get();
}

结果: 

 我们将类A和类B中的一个的智能指针修改为weak_ptr(代码中将类B修改),观察结果我们会发现成功析构了动态空间。

就是因为weak_ptr不会使得引用计数+1,所以类A的引用计数为1,main中析构pa之后,引用计数就变成了0,释放空间。

当我们需要使用类B中的智能指针访问其指向的空间的时候,我们就可以将其转化为shared_ptr然后再去访问。

 

weak_ptr的empired()函数 

我们使用weak_ptr指向一块空间,这块空间也被一个智能指针shared_ptr管理着。

我们无法直接使用weak_ptr访问对应空间,需要使用lock()转化为shared_ptr然后才能访问,但是我们访问之前,是要确保这块空间是否存在的。

因为weak_ptr不会影响引用计数,所以它指向一块空间之后,这块空间的释放不由它决定。

所以当管理这块空间的shared_ptr的对象析构的时候,引用计数变为0,这块空间已经被释放了,但是weak_ptr还不知道我们将它转换为shared_ptr访问其指向的空间会出问题(导致程序中断)。(因为这个空间已经释放了)

  • bool empired();  // 用来判断weak_ptr指向的空间是否被释放。 

    如果被释放,返回true;如果没有被释放,返回false。
建议:
所以在我们使用weak_ptr访问其空间的时候(要使用lock()转化为shared_ptr才能访问),防止程序突然中断(因为其访问的空间被释放了),我们在访问的时候需要调用empired()函数判断一下weak_ptr指向的空间是否已经被释放了如果释放了,就不要访问。没有释放就可以访问。(if判断)

代码: 


int main(void) {
	std::weak_ptr<int> w;

	{
		std::shared_ptr<int> p = std::make_shared<int>(5);
		std::weak_ptr<int> w = p;
	}

	if (w.expired()) {  // expired()被释放返回true
		std::cout << "weak_ptr对应的空间已经被释放" << std::endl;
	}
	else {
		std::shared_ptr<int> p = w.lock();
		std::cout << *p << std::endl;
	}

	std::cin.get();
}

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

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

相关文章

鸿蒙入门11-DataPanel组件

数据面板组件 用于将多个数据的占比情况使用 占比图 进行展示 参数 参数形式 &#xff1a; DataPanel( options:{ values: number[], max?: number, type?: DataPanelType } ) 参数名 参数类型 是否必填 默认值 参数描述 values number[] 是 - 数据值列表 最大支持…

Android Studio的button点击事件

xml添加onClick调用方法 public class MainActivity extends AppCompatActivity {// 创建系统时间的文本控件TextView systemTimeTextView;Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activit…

Springboot+Vue项目-基于Java+MySQL的海滨体育馆管理系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

Jmeter工具+ant+jenkins实现持续集成

jmeterantjenkins持续集成 一、下载并配置jmeter 首先下载jmeter工具&#xff0c;并配置好环境变量&#xff1b;参考&#xff1a; jmeter默认保存的是.jtl格式的文件&#xff0c;要设置一下bin/jmeter.properties,文件内容&#xff0c;保存jmeter.save.saveservice.output_f…

【linux】Bad owner or permissions on

在root用户下执行scp操作向另外一个节点拷贝文件时发生了如下错误&#xff1a; Bad owner or permissions on /etc/crypto-policies/back-ends/openssh.config 我们查看他的权限时发现它所链接的文件权限为777 解决方法就是&#xff1a; chmod 600 /etc/crypto-policies/back-e…

关于加强电力系统通信与电网调度自动化建设问题的规定

关于加强电力系统通信与电网调度自动化建设问题的规定 为了保障电力系统安全、经济、优质、可靠运行&#xff0c;必须加强电网调度管理和提高技术装备水平。根据当前电网技术装备状况&#xff0c;结合电力系统通信和电网调度自动化的特点&#xff0c;以及今后规划发展的要求&am…

Python基础知识—运算符和if语句(二)

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》 《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 1.输入和输出函数1.1输出函数1.2输入函数 2.常见运算符2.1赋值运算符2.2比较运算符2.3逻辑运算符2.4and逻辑与2.5or逻辑或2.6not逻…

Java后台开发的前置说明

1.知识点逻辑 一个部分 都是先挑重点知识点讲解 然后根据这些重点知识点去完成一个项目的开发 然后在到返回来解决这个部分其他细枝末节的知识点 2.软件开发的分工 我们大致可以将软件开发分成四块&#xff1a; 1.前端开发(比如开发电脑中的京东 htmlcssjavascript) 2.移动开…

Springboot3集成Web、RedisTemplate、Test和knife4j

本例将展示&#xff0c;如何在Springboot3中完成&#xff1a; Redis功能的Web接口实现构建Redis功能的单元测试knife4j自动化生成文档 Redis功能 Pom.xml <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter…

【EdgeBox-8120AI-TX2】Ubuntu18.04 + ROS_ Melodic + 星秒PAVO2单线激光 雷达评测

大家好&#xff0c;我是虎哥&#xff0c;好久不见&#xff0c;最近这断时间出现了一点变故&#xff0c;开始自己创业&#xff0c;很多事需要忙&#xff0c;所以停更了大约大半年&#xff0c;最近一切已经理顺&#xff0c;所以我还是抽空继续我之前的FLAG&#xff0c;CSDN突破十…

Hadoop伪分布式平台搭建

搭建Hadoop伪分布式环境是在单台机器上模拟完整的Hadoop分布式系统&#xff0c;使得所有的Hadoop守护进程&#xff08;如NameNode、DataNode、ResourceManager、NodeManager等&#xff09;都在同一台机器上运行。这样可以在一台机器上体验Hadoop的分布式特性&#xff0c;适合学…

《AI极简经济学》|揭开AI面纱,普通人的机遇与挑战

《AI极简经济学》一书由经济学家阿杰伊阿格拉沃尔、乔舒亚甘斯和阿维戈德法布联袂撰写&#xff0c;他们共同创立了创新颠覆实验室&#xff08;Creative Destruction Lab&#xff0c;简称CDL&#xff09;&#xff0c;实验室的研究领域主要集中在人工智能和决策领域。他们通过《A…

java 词法分析练习

import parser.Parser;import java.util.ArrayList; import java.util.Arrays; import java.util.List;public class Main {public static void main(String[] args) {// 关键词List<String> keyList new ArrayList<>(Arrays.asList("int","String…

汽车组装3D电子说明书更通俗易懂

激光打印机由于造价高、技术更先进&#xff0c;因此在使用和维护上需要更专业的手法&#xff0c;而对于普通客户来说并不具备专业操作激光打印机的技能&#xff0c;为了通俗易懂地让客户理解激光打印机&#xff0c;我们为企业定制了激光打印机3D产品说明书&#xff0c;将为您带…

Trello与Notion的开源替代项目管理利器Focalboard本地安装与远程访问

本篇文章将介绍如何使用 Docker 本地部署 Focalboard 项目管理工具&#xff0c;并且结合 cpolar 内网穿透进行公网访问&#xff0c;实现团队协作&#xff0c;提高工作效率&#xff01; Focalboard 是一个开源项目管理工具&#xff0c;可以替代 Asana、Trello 和 Notion 等软件…

软件项目交付支撑文档有哪些?文档下载获取

软件文档交付清单是指在软件开发项目完成后&#xff0c;开发团队需要准备的一份详细清单&#xff0c;用于确保交付的软件产品符合客户需求并达到预期的质量标准。以下是软件文档交付清单中可能包含的一些关键要素 软件开发文档&#xff1a;这包括需求文档、设计文档、测试文档等…

SignalR中的重连机制和心跳监测机制详解

一. 重连机制 声明&#xff1a;   本节仅介绍重连机制和心跳监测机制&#xff0c;基于Core 3.1框架&#xff0c;至于SignalR其它的一些基本使用&#xff0c;包括引入、Hub、配置等常规操作&#xff0c;在本节中不介绍&#xff0c;后续写Core下的SignalR 说明   默认是没有重…

Linux抓包工具tcpdump

一、tcpdump抓包工具 1.命令格式解析 命令格式&#xff1a;tcpdump option proto dir type proto&#xff08;协议&#xff09; 1.tcp、udp、icmp 2.ip、ipv6 3.arp dir&#xff08;数据的方向 &#xff09; 1.src 192.168.7.130 只抓取源地址是7.130 2.…

【数学建模】DVD在线租赁

2005高教社杯全国大学生数学建模竞赛题目B 随着信息时代的到来&#xff0c;网络成为人们生活中越来越不可或缺的元素之一。许多网站利用其强大的资源和知名度&#xff0c;面向其会员群提供日益专业化和便捷化的服务。例如&#xff0c;音像制品的在线租赁就是一种可行的服务。这…

A Survey of State of the Art on Rumor Detection in Social Network

Abstract 互联网上充斥着谣言帖子&#xff0c;谣言的传播会给社会和谐稳定带来负面影响&#xff0c;影响网络信息生态的健康发展。谣言的不确定性、时效性、主观性等特点&#xff0c;使其不同于一般的虚假网络信息。社交网络谣言检测是社交网络与信息传播研究领域的热点问题&am…